MTerryJack commited on
Commit
cdf9d60
·
verified ·
1 Parent(s): 1aa2e4f

Add files using upload-large-folder tool

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .venv/lib/python3.13/site-packages/sympy/algebras/__init__.py +3 -0
  2. .venv/lib/python3.13/site-packages/sympy/algebras/quaternion.py +1666 -0
  3. .venv/lib/python3.13/site-packages/sympy/assumptions/__init__.py +18 -0
  4. .venv/lib/python3.13/site-packages/sympy/assumptions/ask.py +651 -0
  5. .venv/lib/python3.13/site-packages/sympy/assumptions/ask_generated.py +352 -0
  6. .venv/lib/python3.13/site-packages/sympy/assumptions/assume.py +485 -0
  7. .venv/lib/python3.13/site-packages/sympy/assumptions/cnf.py +445 -0
  8. .venv/lib/python3.13/site-packages/sympy/assumptions/facts.py +270 -0
  9. .venv/lib/python3.13/site-packages/sympy/assumptions/lra_satask.py +286 -0
  10. .venv/lib/python3.13/site-packages/sympy/assumptions/refine.py +405 -0
  11. .venv/lib/python3.13/site-packages/sympy/assumptions/satask.py +369 -0
  12. .venv/lib/python3.13/site-packages/sympy/assumptions/sathandlers.py +322 -0
  13. .venv/lib/python3.13/site-packages/sympy/assumptions/wrapper.py +164 -0
  14. .venv/lib/python3.13/site-packages/sympy/benchmarks/__init__.py +0 -0
  15. .venv/lib/python3.13/site-packages/sympy/benchmarks/bench_discrete_log.py +83 -0
  16. .venv/lib/python3.13/site-packages/sympy/benchmarks/bench_meijerint.py +261 -0
  17. .venv/lib/python3.13/site-packages/sympy/benchmarks/bench_symbench.py +134 -0
  18. .venv/lib/python3.13/site-packages/sympy/codegen/__init__.py +24 -0
  19. .venv/lib/python3.13/site-packages/sympy/codegen/abstract_nodes.py +18 -0
  20. .venv/lib/python3.13/site-packages/sympy/codegen/algorithms.py +180 -0
  21. .venv/lib/python3.13/site-packages/sympy/codegen/approximations.py +187 -0
  22. .venv/lib/python3.13/site-packages/sympy/codegen/ast.py +1906 -0
  23. .venv/lib/python3.13/site-packages/sympy/codegen/cfunctions.py +558 -0
  24. .venv/lib/python3.13/site-packages/sympy/codegen/cnodes.py +156 -0
  25. .venv/lib/python3.13/site-packages/sympy/codegen/cutils.py +8 -0
  26. .venv/lib/python3.13/site-packages/sympy/codegen/cxxnodes.py +14 -0
  27. .venv/lib/python3.13/site-packages/sympy/codegen/fnodes.py +658 -0
  28. .venv/lib/python3.13/site-packages/sympy/codegen/futils.py +40 -0
  29. .venv/lib/python3.13/site-packages/sympy/codegen/matrix_nodes.py +71 -0
  30. .venv/lib/python3.13/site-packages/sympy/codegen/numpy_nodes.py +177 -0
  31. .venv/lib/python3.13/site-packages/sympy/codegen/pynodes.py +11 -0
  32. .venv/lib/python3.13/site-packages/sympy/codegen/pyutils.py +24 -0
  33. .venv/lib/python3.13/site-packages/sympy/codegen/rewriting.py +357 -0
  34. .venv/lib/python3.13/site-packages/sympy/codegen/scipy_nodes.py +79 -0
  35. .venv/lib/python3.13/site-packages/sympy/combinatorics/__init__.py +43 -0
  36. .venv/lib/python3.13/site-packages/sympy/combinatorics/coset_table.py +1259 -0
  37. .venv/lib/python3.13/site-packages/sympy/combinatorics/fp_groups.py +1352 -0
  38. .venv/lib/python3.13/site-packages/sympy/combinatorics/free_groups.py +1360 -0
  39. .venv/lib/python3.13/site-packages/sympy/combinatorics/galois.py +611 -0
  40. .venv/lib/python3.13/site-packages/sympy/combinatorics/generators.py +301 -0
  41. .venv/lib/python3.13/site-packages/sympy/combinatorics/graycode.py +430 -0
  42. .venv/lib/python3.13/site-packages/sympy/combinatorics/group_constructs.py +61 -0
  43. .venv/lib/python3.13/site-packages/sympy/combinatorics/group_numbers.py +294 -0
  44. .venv/lib/python3.13/site-packages/sympy/combinatorics/homomorphisms.py +549 -0
  45. .venv/lib/python3.13/site-packages/sympy/combinatorics/named_groups.py +332 -0
  46. .venv/lib/python3.13/site-packages/sympy/combinatorics/partitions.py +745 -0
  47. .venv/lib/python3.13/site-packages/sympy/combinatorics/pc_groups.py +710 -0
  48. .venv/lib/python3.13/site-packages/sympy/combinatorics/perm_groups.py +0 -0
  49. .venv/lib/python3.13/site-packages/sympy/combinatorics/permutations.py +3114 -0
  50. .venv/lib/python3.13/site-packages/sympy/combinatorics/polyhedron.py +1019 -0
.venv/lib/python3.13/site-packages/sympy/algebras/__init__.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ from .quaternion import Quaternion
2
+
3
+ __all__ = ["Quaternion",]
.venv/lib/python3.13/site-packages/sympy/algebras/quaternion.py ADDED
@@ -0,0 +1,1666 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sympy.core.numbers import Rational
2
+ from sympy.core.singleton import S
3
+ from sympy.core.relational import is_eq
4
+ from sympy.functions.elementary.complexes import (conjugate, im, re, sign)
5
+ from sympy.functions.elementary.exponential import (exp, log as ln)
6
+ from sympy.functions.elementary.miscellaneous import sqrt
7
+ from sympy.functions.elementary.trigonometric import (acos, asin, atan2)
8
+ from sympy.functions.elementary.trigonometric import (cos, sin)
9
+ from sympy.simplify.trigsimp import trigsimp
10
+ from sympy.integrals.integrals import integrate
11
+ from sympy.matrices.dense import MutableDenseMatrix as Matrix
12
+ from sympy.core.sympify import sympify, _sympify
13
+ from sympy.core.expr import Expr
14
+ from sympy.core.logic import fuzzy_not, fuzzy_or
15
+ from sympy.utilities.misc import as_int
16
+
17
+ from mpmath.libmp.libmpf import prec_to_dps
18
+
19
+
20
+ def _check_norm(elements, norm):
21
+ """validate if input norm is consistent"""
22
+ if norm is not None and norm.is_number:
23
+ if norm.is_positive is False:
24
+ raise ValueError("Input norm must be positive.")
25
+
26
+ numerical = all(i.is_number and i.is_real is True for i in elements)
27
+ if numerical and is_eq(norm**2, sum(i**2 for i in elements)) is False:
28
+ raise ValueError("Incompatible value for norm.")
29
+
30
+
31
+ def _is_extrinsic(seq):
32
+ """validate seq and return True if seq is lowercase and False if uppercase"""
33
+ if type(seq) != str:
34
+ raise ValueError('Expected seq to be a string.')
35
+ if len(seq) != 3:
36
+ raise ValueError("Expected 3 axes, got `{}`.".format(seq))
37
+
38
+ intrinsic = seq.isupper()
39
+ extrinsic = seq.islower()
40
+ if not (intrinsic or extrinsic):
41
+ raise ValueError("seq must either be fully uppercase (for extrinsic "
42
+ "rotations), or fully lowercase, for intrinsic "
43
+ "rotations).")
44
+
45
+ i, j, k = seq.lower()
46
+ if (i == j) or (j == k):
47
+ raise ValueError("Consecutive axes must be different")
48
+
49
+ bad = set(seq) - set('xyzXYZ')
50
+ if bad:
51
+ raise ValueError("Expected axes from `seq` to be from "
52
+ "['x', 'y', 'z'] or ['X', 'Y', 'Z'], "
53
+ "got {}".format(''.join(bad)))
54
+
55
+ return extrinsic
56
+
57
+
58
+ class Quaternion(Expr):
59
+ """Provides basic quaternion operations.
60
+ Quaternion objects can be instantiated as ``Quaternion(a, b, c, d)``
61
+ as in $q = a + bi + cj + dk$.
62
+
63
+ Parameters
64
+ ==========
65
+
66
+ norm : None or number
67
+ Pre-defined quaternion norm. If a value is given, Quaternion.norm
68
+ returns this pre-defined value instead of calculating the norm
69
+
70
+ Examples
71
+ ========
72
+
73
+ >>> from sympy import Quaternion
74
+ >>> q = Quaternion(1, 2, 3, 4)
75
+ >>> q
76
+ 1 + 2*i + 3*j + 4*k
77
+
78
+ Quaternions over complex fields can be defined as:
79
+
80
+ >>> from sympy import Quaternion
81
+ >>> from sympy import symbols, I
82
+ >>> x = symbols('x')
83
+ >>> q1 = Quaternion(x, x**3, x, x**2, real_field = False)
84
+ >>> q2 = Quaternion(3 + 4*I, 2 + 5*I, 0, 7 + 8*I, real_field = False)
85
+ >>> q1
86
+ x + x**3*i + x*j + x**2*k
87
+ >>> q2
88
+ (3 + 4*I) + (2 + 5*I)*i + 0*j + (7 + 8*I)*k
89
+
90
+ Defining symbolic unit quaternions:
91
+
92
+ >>> from sympy import Quaternion
93
+ >>> from sympy.abc import w, x, y, z
94
+ >>> q = Quaternion(w, x, y, z, norm=1)
95
+ >>> q
96
+ w + x*i + y*j + z*k
97
+ >>> q.norm()
98
+ 1
99
+
100
+ References
101
+ ==========
102
+
103
+ .. [1] https://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/
104
+ .. [2] https://en.wikipedia.org/wiki/Quaternion
105
+
106
+ """
107
+ _op_priority = 11.0
108
+
109
+ is_commutative = False
110
+
111
+ def __new__(cls, a=0, b=0, c=0, d=0, real_field=True, norm=None):
112
+ a, b, c, d = map(sympify, (a, b, c, d))
113
+
114
+ if any(i.is_commutative is False for i in [a, b, c, d]):
115
+ raise ValueError("arguments have to be commutative")
116
+ obj = super().__new__(cls, a, b, c, d)
117
+ obj._real_field = real_field
118
+ obj.set_norm(norm)
119
+ return obj
120
+
121
+ def set_norm(self, norm):
122
+ """Sets norm of an already instantiated quaternion.
123
+
124
+ Parameters
125
+ ==========
126
+
127
+ norm : None or number
128
+ Pre-defined quaternion norm. If a value is given, Quaternion.norm
129
+ returns this pre-defined value instead of calculating the norm
130
+
131
+ Examples
132
+ ========
133
+
134
+ >>> from sympy import Quaternion
135
+ >>> from sympy.abc import a, b, c, d
136
+ >>> q = Quaternion(a, b, c, d)
137
+ >>> q.norm()
138
+ sqrt(a**2 + b**2 + c**2 + d**2)
139
+
140
+ Setting the norm:
141
+
142
+ >>> q.set_norm(1)
143
+ >>> q.norm()
144
+ 1
145
+
146
+ Removing set norm:
147
+
148
+ >>> q.set_norm(None)
149
+ >>> q.norm()
150
+ sqrt(a**2 + b**2 + c**2 + d**2)
151
+
152
+ """
153
+ norm = sympify(norm)
154
+ _check_norm(self.args, norm)
155
+ self._norm = norm
156
+
157
+ @property
158
+ def a(self):
159
+ return self.args[0]
160
+
161
+ @property
162
+ def b(self):
163
+ return self.args[1]
164
+
165
+ @property
166
+ def c(self):
167
+ return self.args[2]
168
+
169
+ @property
170
+ def d(self):
171
+ return self.args[3]
172
+
173
+ @property
174
+ def real_field(self):
175
+ return self._real_field
176
+
177
+ @property
178
+ def product_matrix_left(self):
179
+ r"""Returns 4 x 4 Matrix equivalent to a Hamilton product from the
180
+ left. This can be useful when treating quaternion elements as column
181
+ vectors. Given a quaternion $q = a + bi + cj + dk$ where a, b, c and d
182
+ are real numbers, the product matrix from the left is:
183
+
184
+ .. math::
185
+
186
+ M = \begin{bmatrix} a &-b &-c &-d \\
187
+ b & a &-d & c \\
188
+ c & d & a &-b \\
189
+ d &-c & b & a \end{bmatrix}
190
+
191
+ Examples
192
+ ========
193
+
194
+ >>> from sympy import Quaternion
195
+ >>> from sympy.abc import a, b, c, d
196
+ >>> q1 = Quaternion(1, 0, 0, 1)
197
+ >>> q2 = Quaternion(a, b, c, d)
198
+ >>> q1.product_matrix_left
199
+ Matrix([
200
+ [1, 0, 0, -1],
201
+ [0, 1, -1, 0],
202
+ [0, 1, 1, 0],
203
+ [1, 0, 0, 1]])
204
+
205
+ >>> q1.product_matrix_left * q2.to_Matrix()
206
+ Matrix([
207
+ [a - d],
208
+ [b - c],
209
+ [b + c],
210
+ [a + d]])
211
+
212
+ This is equivalent to:
213
+
214
+ >>> (q1 * q2).to_Matrix()
215
+ Matrix([
216
+ [a - d],
217
+ [b - c],
218
+ [b + c],
219
+ [a + d]])
220
+ """
221
+ return Matrix([
222
+ [self.a, -self.b, -self.c, -self.d],
223
+ [self.b, self.a, -self.d, self.c],
224
+ [self.c, self.d, self.a, -self.b],
225
+ [self.d, -self.c, self.b, self.a]])
226
+
227
+ @property
228
+ def product_matrix_right(self):
229
+ r"""Returns 4 x 4 Matrix equivalent to a Hamilton product from the
230
+ right. This can be useful when treating quaternion elements as column
231
+ vectors. Given a quaternion $q = a + bi + cj + dk$ where a, b, c and d
232
+ are real numbers, the product matrix from the left is:
233
+
234
+ .. math::
235
+
236
+ M = \begin{bmatrix} a &-b &-c &-d \\
237
+ b & a & d &-c \\
238
+ c &-d & a & b \\
239
+ d & c &-b & a \end{bmatrix}
240
+
241
+
242
+ Examples
243
+ ========
244
+
245
+ >>> from sympy import Quaternion
246
+ >>> from sympy.abc import a, b, c, d
247
+ >>> q1 = Quaternion(a, b, c, d)
248
+ >>> q2 = Quaternion(1, 0, 0, 1)
249
+ >>> q2.product_matrix_right
250
+ Matrix([
251
+ [1, 0, 0, -1],
252
+ [0, 1, 1, 0],
253
+ [0, -1, 1, 0],
254
+ [1, 0, 0, 1]])
255
+
256
+ Note the switched arguments: the matrix represents the quaternion on
257
+ the right, but is still considered as a matrix multiplication from the
258
+ left.
259
+
260
+ >>> q2.product_matrix_right * q1.to_Matrix()
261
+ Matrix([
262
+ [ a - d],
263
+ [ b + c],
264
+ [-b + c],
265
+ [ a + d]])
266
+
267
+ This is equivalent to:
268
+
269
+ >>> (q1 * q2).to_Matrix()
270
+ Matrix([
271
+ [ a - d],
272
+ [ b + c],
273
+ [-b + c],
274
+ [ a + d]])
275
+ """
276
+ return Matrix([
277
+ [self.a, -self.b, -self.c, -self.d],
278
+ [self.b, self.a, self.d, -self.c],
279
+ [self.c, -self.d, self.a, self.b],
280
+ [self.d, self.c, -self.b, self.a]])
281
+
282
+ def to_Matrix(self, vector_only=False):
283
+ """Returns elements of quaternion as a column vector.
284
+ By default, a ``Matrix`` of length 4 is returned, with the real part as the
285
+ first element.
286
+ If ``vector_only`` is ``True``, returns only imaginary part as a Matrix of
287
+ length 3.
288
+
289
+ Parameters
290
+ ==========
291
+
292
+ vector_only : bool
293
+ If True, only imaginary part is returned.
294
+ Default value: False
295
+
296
+ Returns
297
+ =======
298
+
299
+ Matrix
300
+ A column vector constructed by the elements of the quaternion.
301
+
302
+ Examples
303
+ ========
304
+
305
+ >>> from sympy import Quaternion
306
+ >>> from sympy.abc import a, b, c, d
307
+ >>> q = Quaternion(a, b, c, d)
308
+ >>> q
309
+ a + b*i + c*j + d*k
310
+
311
+ >>> q.to_Matrix()
312
+ Matrix([
313
+ [a],
314
+ [b],
315
+ [c],
316
+ [d]])
317
+
318
+
319
+ >>> q.to_Matrix(vector_only=True)
320
+ Matrix([
321
+ [b],
322
+ [c],
323
+ [d]])
324
+
325
+ """
326
+ if vector_only:
327
+ return Matrix(self.args[1:])
328
+ else:
329
+ return Matrix(self.args)
330
+
331
+ @classmethod
332
+ def from_Matrix(cls, elements):
333
+ """Returns quaternion from elements of a column vector`.
334
+ If vector_only is True, returns only imaginary part as a Matrix of
335
+ length 3.
336
+
337
+ Parameters
338
+ ==========
339
+
340
+ elements : Matrix, list or tuple of length 3 or 4. If length is 3,
341
+ assume real part is zero.
342
+ Default value: False
343
+
344
+ Returns
345
+ =======
346
+
347
+ Quaternion
348
+ A quaternion created from the input elements.
349
+
350
+ Examples
351
+ ========
352
+
353
+ >>> from sympy import Quaternion
354
+ >>> from sympy.abc import a, b, c, d
355
+ >>> q = Quaternion.from_Matrix([a, b, c, d])
356
+ >>> q
357
+ a + b*i + c*j + d*k
358
+
359
+ >>> q = Quaternion.from_Matrix([b, c, d])
360
+ >>> q
361
+ 0 + b*i + c*j + d*k
362
+
363
+ """
364
+ length = len(elements)
365
+ if length != 3 and length != 4:
366
+ raise ValueError("Input elements must have length 3 or 4, got {} "
367
+ "elements".format(length))
368
+
369
+ if length == 3:
370
+ return Quaternion(0, *elements)
371
+ else:
372
+ return Quaternion(*elements)
373
+
374
+ @classmethod
375
+ def from_euler(cls, angles, seq):
376
+ """Returns quaternion equivalent to rotation represented by the Euler
377
+ angles, in the sequence defined by ``seq``.
378
+
379
+ Parameters
380
+ ==========
381
+
382
+ angles : list, tuple or Matrix of 3 numbers
383
+ The Euler angles (in radians).
384
+ seq : string of length 3
385
+ Represents the sequence of rotations.
386
+ For extrinsic rotations, seq must be all lowercase and its elements
387
+ must be from the set ``{'x', 'y', 'z'}``
388
+ For intrinsic rotations, seq must be all uppercase and its elements
389
+ must be from the set ``{'X', 'Y', 'Z'}``
390
+
391
+ Returns
392
+ =======
393
+
394
+ Quaternion
395
+ The normalized rotation quaternion calculated from the Euler angles
396
+ in the given sequence.
397
+
398
+ Examples
399
+ ========
400
+
401
+ >>> from sympy import Quaternion
402
+ >>> from sympy import pi
403
+ >>> q = Quaternion.from_euler([pi/2, 0, 0], 'xyz')
404
+ >>> q
405
+ sqrt(2)/2 + sqrt(2)/2*i + 0*j + 0*k
406
+
407
+ >>> q = Quaternion.from_euler([0, pi/2, pi] , 'zyz')
408
+ >>> q
409
+ 0 + (-sqrt(2)/2)*i + 0*j + sqrt(2)/2*k
410
+
411
+ >>> q = Quaternion.from_euler([0, pi/2, pi] , 'ZYZ')
412
+ >>> q
413
+ 0 + sqrt(2)/2*i + 0*j + sqrt(2)/2*k
414
+
415
+ """
416
+
417
+ if len(angles) != 3:
418
+ raise ValueError("3 angles must be given.")
419
+
420
+ extrinsic = _is_extrinsic(seq)
421
+ i, j, k = seq.lower()
422
+
423
+ # get elementary basis vectors
424
+ ei = [1 if n == i else 0 for n in 'xyz']
425
+ ej = [1 if n == j else 0 for n in 'xyz']
426
+ ek = [1 if n == k else 0 for n in 'xyz']
427
+
428
+ # calculate distinct quaternions
429
+ qi = cls.from_axis_angle(ei, angles[0])
430
+ qj = cls.from_axis_angle(ej, angles[1])
431
+ qk = cls.from_axis_angle(ek, angles[2])
432
+
433
+ if extrinsic:
434
+ return trigsimp(qk * qj * qi)
435
+ else:
436
+ return trigsimp(qi * qj * qk)
437
+
438
+ def to_euler(self, seq, angle_addition=True, avoid_square_root=False):
439
+ r"""Returns Euler angles representing same rotation as the quaternion,
440
+ in the sequence given by ``seq``. This implements the method described
441
+ in [1]_.
442
+
443
+ For degenerate cases (gymbal lock cases), the third angle is
444
+ set to zero.
445
+
446
+ Parameters
447
+ ==========
448
+
449
+ seq : string of length 3
450
+ Represents the sequence of rotations.
451
+ For extrinsic rotations, seq must be all lowercase and its elements
452
+ must be from the set ``{'x', 'y', 'z'}``
453
+ For intrinsic rotations, seq must be all uppercase and its elements
454
+ must be from the set ``{'X', 'Y', 'Z'}``
455
+
456
+ angle_addition : bool
457
+ When True, first and third angles are given as an addition and
458
+ subtraction of two simpler ``atan2`` expressions. When False, the
459
+ first and third angles are each given by a single more complicated
460
+ ``atan2`` expression. This equivalent expression is given by:
461
+
462
+ .. math::
463
+
464
+ \operatorname{atan_2} (b,a) \pm \operatorname{atan_2} (d,c) =
465
+ \operatorname{atan_2} (bc\pm ad, ac\mp bd)
466
+
467
+ Default value: True
468
+
469
+ avoid_square_root : bool
470
+ When True, the second angle is calculated with an expression based
471
+ on ``acos``, which is slightly more complicated but avoids a square
472
+ root. When False, second angle is calculated with ``atan2``, which
473
+ is simpler and can be better for numerical reasons (some
474
+ numerical implementations of ``acos`` have problems near zero).
475
+ Default value: False
476
+
477
+
478
+ Returns
479
+ =======
480
+
481
+ Tuple
482
+ The Euler angles calculated from the quaternion
483
+
484
+ Examples
485
+ ========
486
+
487
+ >>> from sympy import Quaternion
488
+ >>> from sympy.abc import a, b, c, d
489
+ >>> euler = Quaternion(a, b, c, d).to_euler('zyz')
490
+ >>> euler
491
+ (-atan2(-b, c) + atan2(d, a),
492
+ 2*atan2(sqrt(b**2 + c**2), sqrt(a**2 + d**2)),
493
+ atan2(-b, c) + atan2(d, a))
494
+
495
+
496
+ References
497
+ ==========
498
+
499
+ .. [1] https://doi.org/10.1371/journal.pone.0276302
500
+
501
+ """
502
+ if self.is_zero_quaternion():
503
+ raise ValueError('Cannot convert a quaternion with norm 0.')
504
+
505
+ angles = [0, 0, 0]
506
+
507
+ extrinsic = _is_extrinsic(seq)
508
+ i, j, k = seq.lower()
509
+
510
+ # get index corresponding to elementary basis vectors
511
+ i = 'xyz'.index(i) + 1
512
+ j = 'xyz'.index(j) + 1
513
+ k = 'xyz'.index(k) + 1
514
+
515
+ if not extrinsic:
516
+ i, k = k, i
517
+
518
+ # check if sequence is symmetric
519
+ symmetric = i == k
520
+ if symmetric:
521
+ k = 6 - i - j
522
+
523
+ # parity of the permutation
524
+ sign = (i - j) * (j - k) * (k - i) // 2
525
+
526
+ # permutate elements
527
+ elements = [self.a, self.b, self.c, self.d]
528
+ a = elements[0]
529
+ b = elements[i]
530
+ c = elements[j]
531
+ d = elements[k] * sign
532
+
533
+ if not symmetric:
534
+ a, b, c, d = a - c, b + d, c + a, d - b
535
+
536
+ if avoid_square_root:
537
+ if symmetric:
538
+ n2 = self.norm()**2
539
+ angles[1] = acos((a * a + b * b - c * c - d * d) / n2)
540
+ else:
541
+ n2 = 2 * self.norm()**2
542
+ angles[1] = asin((c * c + d * d - a * a - b * b) / n2)
543
+ else:
544
+ angles[1] = 2 * atan2(sqrt(c * c + d * d), sqrt(a * a + b * b))
545
+ if not symmetric:
546
+ angles[1] -= S.Pi / 2
547
+
548
+ # Check for singularities in numerical cases
549
+ case = 0
550
+ if is_eq(c, S.Zero) and is_eq(d, S.Zero):
551
+ case = 1
552
+ if is_eq(a, S.Zero) and is_eq(b, S.Zero):
553
+ case = 2
554
+
555
+ if case == 0:
556
+ if angle_addition:
557
+ angles[0] = atan2(b, a) + atan2(d, c)
558
+ angles[2] = atan2(b, a) - atan2(d, c)
559
+ else:
560
+ angles[0] = atan2(b*c + a*d, a*c - b*d)
561
+ angles[2] = atan2(b*c - a*d, a*c + b*d)
562
+
563
+ else: # any degenerate case
564
+ angles[2 * (not extrinsic)] = S.Zero
565
+ if case == 1:
566
+ angles[2 * extrinsic] = 2 * atan2(b, a)
567
+ else:
568
+ angles[2 * extrinsic] = 2 * atan2(d, c)
569
+ angles[2 * extrinsic] *= (-1 if extrinsic else 1)
570
+
571
+ # for Tait-Bryan angles
572
+ if not symmetric:
573
+ angles[0] *= sign
574
+
575
+ if extrinsic:
576
+ return tuple(angles[::-1])
577
+ else:
578
+ return tuple(angles)
579
+
580
+ @classmethod
581
+ def from_axis_angle(cls, vector, angle):
582
+ """Returns a rotation quaternion given the axis and the angle of rotation.
583
+
584
+ Parameters
585
+ ==========
586
+
587
+ vector : tuple of three numbers
588
+ The vector representation of the given axis.
589
+ angle : number
590
+ The angle by which axis is rotated (in radians).
591
+
592
+ Returns
593
+ =======
594
+
595
+ Quaternion
596
+ The normalized rotation quaternion calculated from the given axis and the angle of rotation.
597
+
598
+ Examples
599
+ ========
600
+
601
+ >>> from sympy import Quaternion
602
+ >>> from sympy import pi, sqrt
603
+ >>> q = Quaternion.from_axis_angle((sqrt(3)/3, sqrt(3)/3, sqrt(3)/3), 2*pi/3)
604
+ >>> q
605
+ 1/2 + 1/2*i + 1/2*j + 1/2*k
606
+
607
+ """
608
+ (x, y, z) = vector
609
+ norm = sqrt(x**2 + y**2 + z**2)
610
+ (x, y, z) = (x / norm, y / norm, z / norm)
611
+ s = sin(angle * S.Half)
612
+ a = cos(angle * S.Half)
613
+ b = x * s
614
+ c = y * s
615
+ d = z * s
616
+
617
+ # note that this quaternion is already normalized by construction:
618
+ # c^2 + (s*x)^2 + (s*y)^2 + (s*z)^2 = c^2 + s^2*(x^2 + y^2 + z^2) = c^2 + s^2 * 1 = c^2 + s^2 = 1
619
+ # so, what we return is a normalized quaternion
620
+
621
+ return cls(a, b, c, d)
622
+
623
+ @classmethod
624
+ def from_rotation_matrix(cls, M):
625
+ """Returns the equivalent quaternion of a matrix. The quaternion will be normalized
626
+ only if the matrix is special orthogonal (orthogonal and det(M) = 1).
627
+
628
+ Parameters
629
+ ==========
630
+
631
+ M : Matrix
632
+ Input matrix to be converted to equivalent quaternion. M must be special
633
+ orthogonal (orthogonal and det(M) = 1) for the quaternion to be normalized.
634
+
635
+ Returns
636
+ =======
637
+
638
+ Quaternion
639
+ The quaternion equivalent to given matrix.
640
+
641
+ Examples
642
+ ========
643
+
644
+ >>> from sympy import Quaternion
645
+ >>> from sympy import Matrix, symbols, cos, sin, trigsimp
646
+ >>> x = symbols('x')
647
+ >>> M = Matrix([[cos(x), -sin(x), 0], [sin(x), cos(x), 0], [0, 0, 1]])
648
+ >>> q = trigsimp(Quaternion.from_rotation_matrix(M))
649
+ >>> q
650
+ sqrt(2)*sqrt(cos(x) + 1)/2 + 0*i + 0*j + sqrt(2 - 2*cos(x))*sign(sin(x))/2*k
651
+
652
+ """
653
+
654
+ absQ = M.det()**Rational(1, 3)
655
+
656
+ a = sqrt(absQ + M[0, 0] + M[1, 1] + M[2, 2]) / 2
657
+ b = sqrt(absQ + M[0, 0] - M[1, 1] - M[2, 2]) / 2
658
+ c = sqrt(absQ - M[0, 0] + M[1, 1] - M[2, 2]) / 2
659
+ d = sqrt(absQ - M[0, 0] - M[1, 1] + M[2, 2]) / 2
660
+
661
+ b = b * sign(M[2, 1] - M[1, 2])
662
+ c = c * sign(M[0, 2] - M[2, 0])
663
+ d = d * sign(M[1, 0] - M[0, 1])
664
+
665
+ return Quaternion(a, b, c, d)
666
+
667
+ def __add__(self, other):
668
+ return self.add(other)
669
+
670
+ def __radd__(self, other):
671
+ return self.add(other)
672
+
673
+ def __sub__(self, other):
674
+ return self.add(other*-1)
675
+
676
+ def __mul__(self, other):
677
+ return self._generic_mul(self, _sympify(other))
678
+
679
+ def __rmul__(self, other):
680
+ return self._generic_mul(_sympify(other), self)
681
+
682
+ def __pow__(self, p):
683
+ return self.pow(p)
684
+
685
+ def __neg__(self):
686
+ return Quaternion(-self.a, -self.b, -self.c, -self.d)
687
+
688
+ def __truediv__(self, other):
689
+ return self * sympify(other)**-1
690
+
691
+ def __rtruediv__(self, other):
692
+ return sympify(other) * self**-1
693
+
694
+ def _eval_Integral(self, *args):
695
+ return self.integrate(*args)
696
+
697
+ def diff(self, *symbols, **kwargs):
698
+ kwargs.setdefault('evaluate', True)
699
+ return self.func(*[a.diff(*symbols, **kwargs) for a in self.args])
700
+
701
+ def add(self, other):
702
+ """Adds quaternions.
703
+
704
+ Parameters
705
+ ==========
706
+
707
+ other : Quaternion
708
+ The quaternion to add to current (self) quaternion.
709
+
710
+ Returns
711
+ =======
712
+
713
+ Quaternion
714
+ The resultant quaternion after adding self to other
715
+
716
+ Examples
717
+ ========
718
+
719
+ >>> from sympy import Quaternion
720
+ >>> from sympy import symbols
721
+ >>> q1 = Quaternion(1, 2, 3, 4)
722
+ >>> q2 = Quaternion(5, 6, 7, 8)
723
+ >>> q1.add(q2)
724
+ 6 + 8*i + 10*j + 12*k
725
+ >>> q1 + 5
726
+ 6 + 2*i + 3*j + 4*k
727
+ >>> x = symbols('x', real = True)
728
+ >>> q1.add(x)
729
+ (x + 1) + 2*i + 3*j + 4*k
730
+
731
+ Quaternions over complex fields :
732
+
733
+ >>> from sympy import Quaternion
734
+ >>> from sympy import I
735
+ >>> q3 = Quaternion(3 + 4*I, 2 + 5*I, 0, 7 + 8*I, real_field = False)
736
+ >>> q3.add(2 + 3*I)
737
+ (5 + 7*I) + (2 + 5*I)*i + 0*j + (7 + 8*I)*k
738
+
739
+ """
740
+ q1 = self
741
+ q2 = sympify(other)
742
+
743
+ # If q2 is a number or a SymPy expression instead of a quaternion
744
+ if not isinstance(q2, Quaternion):
745
+ if q1.real_field and q2.is_complex:
746
+ return Quaternion(re(q2) + q1.a, im(q2) + q1.b, q1.c, q1.d)
747
+ elif q2.is_commutative:
748
+ return Quaternion(q1.a + q2, q1.b, q1.c, q1.d)
749
+ else:
750
+ raise ValueError("Only commutative expressions can be added with a Quaternion.")
751
+
752
+ return Quaternion(q1.a + q2.a, q1.b + q2.b, q1.c + q2.c, q1.d
753
+ + q2.d)
754
+
755
+ def mul(self, other):
756
+ """Multiplies quaternions.
757
+
758
+ Parameters
759
+ ==========
760
+
761
+ other : Quaternion or symbol
762
+ The quaternion to multiply to current (self) quaternion.
763
+
764
+ Returns
765
+ =======
766
+
767
+ Quaternion
768
+ The resultant quaternion after multiplying self with other
769
+
770
+ Examples
771
+ ========
772
+
773
+ >>> from sympy import Quaternion
774
+ >>> from sympy import symbols
775
+ >>> q1 = Quaternion(1, 2, 3, 4)
776
+ >>> q2 = Quaternion(5, 6, 7, 8)
777
+ >>> q1.mul(q2)
778
+ (-60) + 12*i + 30*j + 24*k
779
+ >>> q1.mul(2)
780
+ 2 + 4*i + 6*j + 8*k
781
+ >>> x = symbols('x', real = True)
782
+ >>> q1.mul(x)
783
+ x + 2*x*i + 3*x*j + 4*x*k
784
+
785
+ Quaternions over complex fields :
786
+
787
+ >>> from sympy import Quaternion
788
+ >>> from sympy import I
789
+ >>> q3 = Quaternion(3 + 4*I, 2 + 5*I, 0, 7 + 8*I, real_field = False)
790
+ >>> q3.mul(2 + 3*I)
791
+ (2 + 3*I)*(3 + 4*I) + (2 + 3*I)*(2 + 5*I)*i + 0*j + (2 + 3*I)*(7 + 8*I)*k
792
+
793
+ """
794
+ return self._generic_mul(self, _sympify(other))
795
+
796
+ @staticmethod
797
+ def _generic_mul(q1, q2):
798
+ """Generic multiplication.
799
+
800
+ Parameters
801
+ ==========
802
+
803
+ q1 : Quaternion or symbol
804
+ q2 : Quaternion or symbol
805
+
806
+ It is important to note that if neither q1 nor q2 is a Quaternion,
807
+ this function simply returns q1 * q2.
808
+
809
+ Returns
810
+ =======
811
+
812
+ Quaternion
813
+ The resultant quaternion after multiplying q1 and q2
814
+
815
+ Examples
816
+ ========
817
+
818
+ >>> from sympy import Quaternion
819
+ >>> from sympy import Symbol, S
820
+ >>> q1 = Quaternion(1, 2, 3, 4)
821
+ >>> q2 = Quaternion(5, 6, 7, 8)
822
+ >>> Quaternion._generic_mul(q1, q2)
823
+ (-60) + 12*i + 30*j + 24*k
824
+ >>> Quaternion._generic_mul(q1, S(2))
825
+ 2 + 4*i + 6*j + 8*k
826
+ >>> x = Symbol('x', real = True)
827
+ >>> Quaternion._generic_mul(q1, x)
828
+ x + 2*x*i + 3*x*j + 4*x*k
829
+
830
+ Quaternions over complex fields :
831
+
832
+ >>> from sympy import I
833
+ >>> q3 = Quaternion(3 + 4*I, 2 + 5*I, 0, 7 + 8*I, real_field = False)
834
+ >>> Quaternion._generic_mul(q3, 2 + 3*I)
835
+ (2 + 3*I)*(3 + 4*I) + (2 + 3*I)*(2 + 5*I)*i + 0*j + (2 + 3*I)*(7 + 8*I)*k
836
+
837
+ """
838
+ # None is a Quaternion:
839
+ if not isinstance(q1, Quaternion) and not isinstance(q2, Quaternion):
840
+ return q1 * q2
841
+
842
+ # If q1 is a number or a SymPy expression instead of a quaternion
843
+ if not isinstance(q1, Quaternion):
844
+ if q2.real_field and q1.is_complex:
845
+ return Quaternion(re(q1), im(q1), 0, 0) * q2
846
+ elif q1.is_commutative:
847
+ return Quaternion(q1 * q2.a, q1 * q2.b, q1 * q2.c, q1 * q2.d)
848
+ else:
849
+ raise ValueError("Only commutative expressions can be multiplied with a Quaternion.")
850
+
851
+ # If q2 is a number or a SymPy expression instead of a quaternion
852
+ if not isinstance(q2, Quaternion):
853
+ if q1.real_field and q2.is_complex:
854
+ return q1 * Quaternion(re(q2), im(q2), 0, 0)
855
+ elif q2.is_commutative:
856
+ return Quaternion(q2 * q1.a, q2 * q1.b, q2 * q1.c, q2 * q1.d)
857
+ else:
858
+ raise ValueError("Only commutative expressions can be multiplied with a Quaternion.")
859
+
860
+ # If any of the quaternions has a fixed norm, pre-compute norm
861
+ if q1._norm is None and q2._norm is None:
862
+ norm = None
863
+ else:
864
+ norm = q1.norm() * q2.norm()
865
+
866
+ return Quaternion(-q1.b*q2.b - q1.c*q2.c - q1.d*q2.d + q1.a*q2.a,
867
+ q1.b*q2.a + q1.c*q2.d - q1.d*q2.c + q1.a*q2.b,
868
+ -q1.b*q2.d + q1.c*q2.a + q1.d*q2.b + q1.a*q2.c,
869
+ q1.b*q2.c - q1.c*q2.b + q1.d*q2.a + q1.a * q2.d,
870
+ norm=norm)
871
+
872
+ def _eval_conjugate(self):
873
+ """Returns the conjugate of the quaternion."""
874
+ q = self
875
+ return Quaternion(q.a, -q.b, -q.c, -q.d, norm=q._norm)
876
+
877
+ def norm(self):
878
+ """Returns the norm of the quaternion."""
879
+ if self._norm is None: # check if norm is pre-defined
880
+ q = self
881
+ # trigsimp is used to simplify sin(x)^2 + cos(x)^2 (these terms
882
+ # arise when from_axis_angle is used).
883
+ return sqrt(trigsimp(q.a**2 + q.b**2 + q.c**2 + q.d**2))
884
+
885
+ return self._norm
886
+
887
+ def normalize(self):
888
+ """Returns the normalized form of the quaternion."""
889
+ q = self
890
+ return q * (1/q.norm())
891
+
892
+ def inverse(self):
893
+ """Returns the inverse of the quaternion."""
894
+ q = self
895
+ if not q.norm():
896
+ raise ValueError("Cannot compute inverse for a quaternion with zero norm")
897
+ return conjugate(q) * (1/q.norm()**2)
898
+
899
+ def pow(self, p):
900
+ """Finds the pth power of the quaternion.
901
+
902
+ Parameters
903
+ ==========
904
+
905
+ p : int
906
+ Power to be applied on quaternion.
907
+
908
+ Returns
909
+ =======
910
+
911
+ Quaternion
912
+ Returns the p-th power of the current quaternion.
913
+ Returns the inverse if p = -1.
914
+
915
+ Examples
916
+ ========
917
+
918
+ >>> from sympy import Quaternion
919
+ >>> q = Quaternion(1, 2, 3, 4)
920
+ >>> q.pow(4)
921
+ 668 + (-224)*i + (-336)*j + (-448)*k
922
+
923
+ """
924
+ try:
925
+ q, p = self, as_int(p)
926
+ except ValueError:
927
+ return NotImplemented
928
+
929
+ if p < 0:
930
+ q, p = q.inverse(), -p
931
+
932
+ if p == 1:
933
+ return q
934
+
935
+ res = Quaternion(1, 0, 0, 0)
936
+ while p > 0:
937
+ if p & 1:
938
+ res *= q
939
+ q *= q
940
+ p >>= 1
941
+
942
+ return res
943
+
944
+ def exp(self):
945
+ """Returns the exponential of $q$, given by $e^q$.
946
+
947
+ Returns
948
+ =======
949
+
950
+ Quaternion
951
+ The exponential of the quaternion.
952
+
953
+ Examples
954
+ ========
955
+
956
+ >>> from sympy import Quaternion
957
+ >>> q = Quaternion(1, 2, 3, 4)
958
+ >>> q.exp()
959
+ E*cos(sqrt(29))
960
+ + 2*sqrt(29)*E*sin(sqrt(29))/29*i
961
+ + 3*sqrt(29)*E*sin(sqrt(29))/29*j
962
+ + 4*sqrt(29)*E*sin(sqrt(29))/29*k
963
+
964
+ """
965
+ # exp(q) = e^a(cos||v|| + v/||v||*sin||v||)
966
+ q = self
967
+ vector_norm = sqrt(q.b**2 + q.c**2 + q.d**2)
968
+ a = exp(q.a) * cos(vector_norm)
969
+ b = exp(q.a) * sin(vector_norm) * q.b / vector_norm
970
+ c = exp(q.a) * sin(vector_norm) * q.c / vector_norm
971
+ d = exp(q.a) * sin(vector_norm) * q.d / vector_norm
972
+
973
+ return Quaternion(a, b, c, d)
974
+
975
+ def log(self):
976
+ r"""Returns the logarithm of the quaternion, given by $\log q$.
977
+
978
+ Examples
979
+ ========
980
+
981
+ >>> from sympy import Quaternion
982
+ >>> q = Quaternion(1, 2, 3, 4)
983
+ >>> q.log()
984
+ log(sqrt(30))
985
+ + 2*sqrt(29)*acos(sqrt(30)/30)/29*i
986
+ + 3*sqrt(29)*acos(sqrt(30)/30)/29*j
987
+ + 4*sqrt(29)*acos(sqrt(30)/30)/29*k
988
+
989
+ """
990
+ # log(q) = log||q|| + v/||v||*arccos(a/||q||)
991
+ q = self
992
+ vector_norm = sqrt(q.b**2 + q.c**2 + q.d**2)
993
+ q_norm = q.norm()
994
+ a = ln(q_norm)
995
+ b = q.b * acos(q.a / q_norm) / vector_norm
996
+ c = q.c * acos(q.a / q_norm) / vector_norm
997
+ d = q.d * acos(q.a / q_norm) / vector_norm
998
+
999
+ return Quaternion(a, b, c, d)
1000
+
1001
+ def _eval_subs(self, *args):
1002
+ elements = [i.subs(*args) for i in self.args]
1003
+ norm = self._norm
1004
+ if norm is not None:
1005
+ norm = norm.subs(*args)
1006
+ _check_norm(elements, norm)
1007
+ return Quaternion(*elements, norm=norm)
1008
+
1009
+ def _eval_evalf(self, prec):
1010
+ """Returns the floating point approximations (decimal numbers) of the quaternion.
1011
+
1012
+ Returns
1013
+ =======
1014
+
1015
+ Quaternion
1016
+ Floating point approximations of quaternion(self)
1017
+
1018
+ Examples
1019
+ ========
1020
+
1021
+ >>> from sympy import Quaternion
1022
+ >>> from sympy import sqrt
1023
+ >>> q = Quaternion(1/sqrt(1), 1/sqrt(2), 1/sqrt(3), 1/sqrt(4))
1024
+ >>> q.evalf()
1025
+ 1.00000000000000
1026
+ + 0.707106781186547*i
1027
+ + 0.577350269189626*j
1028
+ + 0.500000000000000*k
1029
+
1030
+ """
1031
+ nprec = prec_to_dps(prec)
1032
+ return Quaternion(*[arg.evalf(n=nprec) for arg in self.args])
1033
+
1034
+ def pow_cos_sin(self, p):
1035
+ """Computes the pth power in the cos-sin form.
1036
+
1037
+ Parameters
1038
+ ==========
1039
+
1040
+ p : int
1041
+ Power to be applied on quaternion.
1042
+
1043
+ Returns
1044
+ =======
1045
+
1046
+ Quaternion
1047
+ The p-th power in the cos-sin form.
1048
+
1049
+ Examples
1050
+ ========
1051
+
1052
+ >>> from sympy import Quaternion
1053
+ >>> q = Quaternion(1, 2, 3, 4)
1054
+ >>> q.pow_cos_sin(4)
1055
+ 900*cos(4*acos(sqrt(30)/30))
1056
+ + 1800*sqrt(29)*sin(4*acos(sqrt(30)/30))/29*i
1057
+ + 2700*sqrt(29)*sin(4*acos(sqrt(30)/30))/29*j
1058
+ + 3600*sqrt(29)*sin(4*acos(sqrt(30)/30))/29*k
1059
+
1060
+ """
1061
+ # q = ||q||*(cos(a) + u*sin(a))
1062
+ # q^p = ||q||^p * (cos(p*a) + u*sin(p*a))
1063
+
1064
+ q = self
1065
+ (v, angle) = q.to_axis_angle()
1066
+ q2 = Quaternion.from_axis_angle(v, p * angle)
1067
+ return q2 * (q.norm()**p)
1068
+
1069
+ def integrate(self, *args):
1070
+ """Computes integration of quaternion.
1071
+
1072
+ Returns
1073
+ =======
1074
+
1075
+ Quaternion
1076
+ Integration of the quaternion(self) with the given variable.
1077
+
1078
+ Examples
1079
+ ========
1080
+
1081
+ Indefinite Integral of quaternion :
1082
+
1083
+ >>> from sympy import Quaternion
1084
+ >>> from sympy.abc import x
1085
+ >>> q = Quaternion(1, 2, 3, 4)
1086
+ >>> q.integrate(x)
1087
+ x + 2*x*i + 3*x*j + 4*x*k
1088
+
1089
+ Definite integral of quaternion :
1090
+
1091
+ >>> from sympy import Quaternion
1092
+ >>> from sympy.abc import x
1093
+ >>> q = Quaternion(1, 2, 3, 4)
1094
+ >>> q.integrate((x, 1, 5))
1095
+ 4 + 8*i + 12*j + 16*k
1096
+
1097
+ """
1098
+ return Quaternion(integrate(self.a, *args), integrate(self.b, *args),
1099
+ integrate(self.c, *args), integrate(self.d, *args))
1100
+
1101
+ @staticmethod
1102
+ def rotate_point(pin, r):
1103
+ """Returns the coordinates of the point pin (a 3 tuple) after rotation.
1104
+
1105
+ Parameters
1106
+ ==========
1107
+
1108
+ pin : tuple
1109
+ A 3-element tuple of coordinates of a point which needs to be
1110
+ rotated.
1111
+ r : Quaternion or tuple
1112
+ Axis and angle of rotation.
1113
+
1114
+ It's important to note that when r is a tuple, it must be of the form
1115
+ (axis, angle)
1116
+
1117
+ Returns
1118
+ =======
1119
+
1120
+ tuple
1121
+ The coordinates of the point after rotation.
1122
+
1123
+ Examples
1124
+ ========
1125
+
1126
+ >>> from sympy import Quaternion
1127
+ >>> from sympy import symbols, trigsimp, cos, sin
1128
+ >>> x = symbols('x')
1129
+ >>> q = Quaternion(cos(x/2), 0, 0, sin(x/2))
1130
+ >>> trigsimp(Quaternion.rotate_point((1, 1, 1), q))
1131
+ (sqrt(2)*cos(x + pi/4), sqrt(2)*sin(x + pi/4), 1)
1132
+ >>> (axis, angle) = q.to_axis_angle()
1133
+ >>> trigsimp(Quaternion.rotate_point((1, 1, 1), (axis, angle)))
1134
+ (sqrt(2)*cos(x + pi/4), sqrt(2)*sin(x + pi/4), 1)
1135
+
1136
+ """
1137
+ if isinstance(r, tuple):
1138
+ # if r is of the form (vector, angle)
1139
+ q = Quaternion.from_axis_angle(r[0], r[1])
1140
+ else:
1141
+ # if r is a quaternion
1142
+ q = r.normalize()
1143
+ pout = q * Quaternion(0, pin[0], pin[1], pin[2]) * conjugate(q)
1144
+ return (pout.b, pout.c, pout.d)
1145
+
1146
+ def to_axis_angle(self):
1147
+ """Returns the axis and angle of rotation of a quaternion.
1148
+
1149
+ Returns
1150
+ =======
1151
+
1152
+ tuple
1153
+ Tuple of (axis, angle)
1154
+
1155
+ Examples
1156
+ ========
1157
+
1158
+ >>> from sympy import Quaternion
1159
+ >>> q = Quaternion(1, 1, 1, 1)
1160
+ >>> (axis, angle) = q.to_axis_angle()
1161
+ >>> axis
1162
+ (sqrt(3)/3, sqrt(3)/3, sqrt(3)/3)
1163
+ >>> angle
1164
+ 2*pi/3
1165
+
1166
+ """
1167
+ q = self
1168
+ if q.a.is_negative:
1169
+ q = q * -1
1170
+
1171
+ q = q.normalize()
1172
+ angle = trigsimp(2 * acos(q.a))
1173
+
1174
+ # Since quaternion is normalised, q.a is less than 1.
1175
+ s = sqrt(1 - q.a*q.a)
1176
+
1177
+ x = trigsimp(q.b / s)
1178
+ y = trigsimp(q.c / s)
1179
+ z = trigsimp(q.d / s)
1180
+
1181
+ v = (x, y, z)
1182
+ t = (v, angle)
1183
+
1184
+ return t
1185
+
1186
+ def to_rotation_matrix(self, v=None, homogeneous=True):
1187
+ """Returns the equivalent rotation transformation matrix of the quaternion
1188
+ which represents rotation about the origin if ``v`` is not passed.
1189
+
1190
+ Parameters
1191
+ ==========
1192
+
1193
+ v : tuple or None
1194
+ Default value: None
1195
+ homogeneous : bool
1196
+ When True, gives an expression that may be more efficient for
1197
+ symbolic calculations but less so for direct evaluation. Both
1198
+ formulas are mathematically equivalent.
1199
+ Default value: True
1200
+
1201
+ Returns
1202
+ =======
1203
+
1204
+ tuple
1205
+ Returns the equivalent rotation transformation matrix of the quaternion
1206
+ which represents rotation about the origin if v is not passed.
1207
+
1208
+ Examples
1209
+ ========
1210
+
1211
+ >>> from sympy import Quaternion
1212
+ >>> from sympy import symbols, trigsimp, cos, sin
1213
+ >>> x = symbols('x')
1214
+ >>> q = Quaternion(cos(x/2), 0, 0, sin(x/2))
1215
+ >>> trigsimp(q.to_rotation_matrix())
1216
+ Matrix([
1217
+ [cos(x), -sin(x), 0],
1218
+ [sin(x), cos(x), 0],
1219
+ [ 0, 0, 1]])
1220
+
1221
+ Generates a 4x4 transformation matrix (used for rotation about a point
1222
+ other than the origin) if the point(v) is passed as an argument.
1223
+ """
1224
+
1225
+ q = self
1226
+ s = q.norm()**-2
1227
+
1228
+ # diagonal elements are different according to parameter normal
1229
+ if homogeneous:
1230
+ m00 = s*(q.a**2 + q.b**2 - q.c**2 - q.d**2)
1231
+ m11 = s*(q.a**2 - q.b**2 + q.c**2 - q.d**2)
1232
+ m22 = s*(q.a**2 - q.b**2 - q.c**2 + q.d**2)
1233
+ else:
1234
+ m00 = 1 - 2*s*(q.c**2 + q.d**2)
1235
+ m11 = 1 - 2*s*(q.b**2 + q.d**2)
1236
+ m22 = 1 - 2*s*(q.b**2 + q.c**2)
1237
+
1238
+ m01 = 2*s*(q.b*q.c - q.d*q.a)
1239
+ m02 = 2*s*(q.b*q.d + q.c*q.a)
1240
+
1241
+ m10 = 2*s*(q.b*q.c + q.d*q.a)
1242
+ m12 = 2*s*(q.c*q.d - q.b*q.a)
1243
+
1244
+ m20 = 2*s*(q.b*q.d - q.c*q.a)
1245
+ m21 = 2*s*(q.c*q.d + q.b*q.a)
1246
+
1247
+ if not v:
1248
+ return Matrix([[m00, m01, m02], [m10, m11, m12], [m20, m21, m22]])
1249
+
1250
+ else:
1251
+ (x, y, z) = v
1252
+
1253
+ m03 = x - x*m00 - y*m01 - z*m02
1254
+ m13 = y - x*m10 - y*m11 - z*m12
1255
+ m23 = z - x*m20 - y*m21 - z*m22
1256
+ m30 = m31 = m32 = 0
1257
+ m33 = 1
1258
+
1259
+ return Matrix([[m00, m01, m02, m03], [m10, m11, m12, m13],
1260
+ [m20, m21, m22, m23], [m30, m31, m32, m33]])
1261
+
1262
+ def scalar_part(self):
1263
+ r"""Returns scalar part($\mathbf{S}(q)$) of the quaternion q.
1264
+
1265
+ Explanation
1266
+ ===========
1267
+
1268
+ Given a quaternion $q = a + bi + cj + dk$, returns $\mathbf{S}(q) = a$.
1269
+
1270
+ Examples
1271
+ ========
1272
+
1273
+ >>> from sympy.algebras.quaternion import Quaternion
1274
+ >>> q = Quaternion(4, 8, 13, 12)
1275
+ >>> q.scalar_part()
1276
+ 4
1277
+
1278
+ """
1279
+
1280
+ return self.a
1281
+
1282
+ def vector_part(self):
1283
+ r"""
1284
+ Returns $\mathbf{V}(q)$, the vector part of the quaternion $q$.
1285
+
1286
+ Explanation
1287
+ ===========
1288
+
1289
+ Given a quaternion $q = a + bi + cj + dk$, returns $\mathbf{V}(q) = bi + cj + dk$.
1290
+
1291
+ Examples
1292
+ ========
1293
+
1294
+ >>> from sympy.algebras.quaternion import Quaternion
1295
+ >>> q = Quaternion(1, 1, 1, 1)
1296
+ >>> q.vector_part()
1297
+ 0 + 1*i + 1*j + 1*k
1298
+
1299
+ >>> q = Quaternion(4, 8, 13, 12)
1300
+ >>> q.vector_part()
1301
+ 0 + 8*i + 13*j + 12*k
1302
+
1303
+ """
1304
+
1305
+ return Quaternion(0, self.b, self.c, self.d)
1306
+
1307
+ def axis(self):
1308
+ r"""
1309
+ Returns $\mathbf{Ax}(q)$, the axis of the quaternion $q$.
1310
+
1311
+ Explanation
1312
+ ===========
1313
+
1314
+ Given a quaternion $q = a + bi + cj + dk$, returns $\mathbf{Ax}(q)$ i.e., the versor of the vector part of that quaternion
1315
+ equal to $\mathbf{U}[\mathbf{V}(q)]$.
1316
+ The axis is always an imaginary unit with square equal to $-1 + 0i + 0j + 0k$.
1317
+
1318
+ Examples
1319
+ ========
1320
+
1321
+ >>> from sympy.algebras.quaternion import Quaternion
1322
+ >>> q = Quaternion(1, 1, 1, 1)
1323
+ >>> q.axis()
1324
+ 0 + sqrt(3)/3*i + sqrt(3)/3*j + sqrt(3)/3*k
1325
+
1326
+ See Also
1327
+ ========
1328
+
1329
+ vector_part
1330
+
1331
+ """
1332
+ axis = self.vector_part().normalize()
1333
+
1334
+ return Quaternion(0, axis.b, axis.c, axis.d)
1335
+
1336
+ def is_pure(self):
1337
+ """
1338
+ Returns true if the quaternion is pure, false if the quaternion is not pure
1339
+ or returns none if it is unknown.
1340
+
1341
+ Explanation
1342
+ ===========
1343
+
1344
+ A pure quaternion (also a vector quaternion) is a quaternion with scalar
1345
+ part equal to 0.
1346
+
1347
+ Examples
1348
+ ========
1349
+
1350
+ >>> from sympy.algebras.quaternion import Quaternion
1351
+ >>> q = Quaternion(0, 8, 13, 12)
1352
+ >>> q.is_pure()
1353
+ True
1354
+
1355
+ See Also
1356
+ ========
1357
+ scalar_part
1358
+
1359
+ """
1360
+
1361
+ return self.a.is_zero
1362
+
1363
+ def is_zero_quaternion(self):
1364
+ """
1365
+ Returns true if the quaternion is a zero quaternion or false if it is not a zero quaternion
1366
+ and None if the value is unknown.
1367
+
1368
+ Explanation
1369
+ ===========
1370
+
1371
+ A zero quaternion is a quaternion with both scalar part and
1372
+ vector part equal to 0.
1373
+
1374
+ Examples
1375
+ ========
1376
+
1377
+ >>> from sympy.algebras.quaternion import Quaternion
1378
+ >>> q = Quaternion(1, 0, 0, 0)
1379
+ >>> q.is_zero_quaternion()
1380
+ False
1381
+
1382
+ >>> q = Quaternion(0, 0, 0, 0)
1383
+ >>> q.is_zero_quaternion()
1384
+ True
1385
+
1386
+ See Also
1387
+ ========
1388
+ scalar_part
1389
+ vector_part
1390
+
1391
+ """
1392
+
1393
+ return self.norm().is_zero
1394
+
1395
+ def angle(self):
1396
+ r"""
1397
+ Returns the angle of the quaternion measured in the real-axis plane.
1398
+
1399
+ Explanation
1400
+ ===========
1401
+
1402
+ Given a quaternion $q = a + bi + cj + dk$ where $a$, $b$, $c$ and $d$
1403
+ are real numbers, returns the angle of the quaternion given by
1404
+
1405
+ .. math::
1406
+ \theta := 2 \operatorname{atan_2}\left(\sqrt{b^2 + c^2 + d^2}, {a}\right)
1407
+
1408
+ Examples
1409
+ ========
1410
+
1411
+ >>> from sympy.algebras.quaternion import Quaternion
1412
+ >>> q = Quaternion(1, 4, 4, 4)
1413
+ >>> q.angle()
1414
+ 2*atan(4*sqrt(3))
1415
+
1416
+ """
1417
+
1418
+ return 2 * atan2(self.vector_part().norm(), self.scalar_part())
1419
+
1420
+
1421
+ def arc_coplanar(self, other):
1422
+ """
1423
+ Returns True if the transformation arcs represented by the input quaternions happen in the same plane.
1424
+
1425
+ Explanation
1426
+ ===========
1427
+
1428
+ Two quaternions are said to be coplanar (in this arc sense) when their axes are parallel.
1429
+ The plane of a quaternion is the one normal to its axis.
1430
+
1431
+ Parameters
1432
+ ==========
1433
+
1434
+ other : a Quaternion
1435
+
1436
+ Returns
1437
+ =======
1438
+
1439
+ True : if the planes of the two quaternions are the same, apart from its orientation/sign.
1440
+ False : if the planes of the two quaternions are not the same, apart from its orientation/sign.
1441
+ None : if plane of either of the quaternion is unknown.
1442
+
1443
+ Examples
1444
+ ========
1445
+
1446
+ >>> from sympy.algebras.quaternion import Quaternion
1447
+ >>> q1 = Quaternion(1, 4, 4, 4)
1448
+ >>> q2 = Quaternion(3, 8, 8, 8)
1449
+ >>> Quaternion.arc_coplanar(q1, q2)
1450
+ True
1451
+
1452
+ >>> q1 = Quaternion(2, 8, 13, 12)
1453
+ >>> Quaternion.arc_coplanar(q1, q2)
1454
+ False
1455
+
1456
+ See Also
1457
+ ========
1458
+
1459
+ vector_coplanar
1460
+ is_pure
1461
+
1462
+ """
1463
+ if (self.is_zero_quaternion()) or (other.is_zero_quaternion()):
1464
+ raise ValueError('Neither of the given quaternions can be 0')
1465
+
1466
+ return fuzzy_or([(self.axis() - other.axis()).is_zero_quaternion(), (self.axis() + other.axis()).is_zero_quaternion()])
1467
+
1468
+ @classmethod
1469
+ def vector_coplanar(cls, q1, q2, q3):
1470
+ r"""
1471
+ Returns True if the axis of the pure quaternions seen as 3D vectors
1472
+ ``q1``, ``q2``, and ``q3`` are coplanar.
1473
+
1474
+ Explanation
1475
+ ===========
1476
+
1477
+ Three pure quaternions are vector coplanar if the quaternions seen as 3D vectors are coplanar.
1478
+
1479
+ Parameters
1480
+ ==========
1481
+
1482
+ q1
1483
+ A pure Quaternion.
1484
+ q2
1485
+ A pure Quaternion.
1486
+ q3
1487
+ A pure Quaternion.
1488
+
1489
+ Returns
1490
+ =======
1491
+
1492
+ True : if the axis of the pure quaternions seen as 3D vectors
1493
+ q1, q2, and q3 are coplanar.
1494
+ False : if the axis of the pure quaternions seen as 3D vectors
1495
+ q1, q2, and q3 are not coplanar.
1496
+ None : if the axis of the pure quaternions seen as 3D vectors
1497
+ q1, q2, and q3 are coplanar is unknown.
1498
+
1499
+ Examples
1500
+ ========
1501
+
1502
+ >>> from sympy.algebras.quaternion import Quaternion
1503
+ >>> q1 = Quaternion(0, 4, 4, 4)
1504
+ >>> q2 = Quaternion(0, 8, 8, 8)
1505
+ >>> q3 = Quaternion(0, 24, 24, 24)
1506
+ >>> Quaternion.vector_coplanar(q1, q2, q3)
1507
+ True
1508
+
1509
+ >>> q1 = Quaternion(0, 8, 16, 8)
1510
+ >>> q2 = Quaternion(0, 8, 3, 12)
1511
+ >>> Quaternion.vector_coplanar(q1, q2, q3)
1512
+ False
1513
+
1514
+ See Also
1515
+ ========
1516
+
1517
+ axis
1518
+ is_pure
1519
+
1520
+ """
1521
+
1522
+ if fuzzy_not(q1.is_pure()) or fuzzy_not(q2.is_pure()) or fuzzy_not(q3.is_pure()):
1523
+ raise ValueError('The given quaternions must be pure')
1524
+
1525
+ M = Matrix([[q1.b, q1.c, q1.d], [q2.b, q2.c, q2.d], [q3.b, q3.c, q3.d]]).det()
1526
+ return M.is_zero
1527
+
1528
+ def parallel(self, other):
1529
+ """
1530
+ Returns True if the two pure quaternions seen as 3D vectors are parallel.
1531
+
1532
+ Explanation
1533
+ ===========
1534
+
1535
+ Two pure quaternions are called parallel when their vector product is commutative which
1536
+ implies that the quaternions seen as 3D vectors have same direction.
1537
+
1538
+ Parameters
1539
+ ==========
1540
+
1541
+ other : a Quaternion
1542
+
1543
+ Returns
1544
+ =======
1545
+
1546
+ True : if the two pure quaternions seen as 3D vectors are parallel.
1547
+ False : if the two pure quaternions seen as 3D vectors are not parallel.
1548
+ None : if the two pure quaternions seen as 3D vectors are parallel is unknown.
1549
+
1550
+ Examples
1551
+ ========
1552
+
1553
+ >>> from sympy.algebras.quaternion import Quaternion
1554
+ >>> q = Quaternion(0, 4, 4, 4)
1555
+ >>> q1 = Quaternion(0, 8, 8, 8)
1556
+ >>> q.parallel(q1)
1557
+ True
1558
+
1559
+ >>> q1 = Quaternion(0, 8, 13, 12)
1560
+ >>> q.parallel(q1)
1561
+ False
1562
+
1563
+ """
1564
+
1565
+ if fuzzy_not(self.is_pure()) or fuzzy_not(other.is_pure()):
1566
+ raise ValueError('The provided quaternions must be pure')
1567
+
1568
+ return (self*other - other*self).is_zero_quaternion()
1569
+
1570
+ def orthogonal(self, other):
1571
+ """
1572
+ Returns the orthogonality of two quaternions.
1573
+
1574
+ Explanation
1575
+ ===========
1576
+
1577
+ Two pure quaternions are called orthogonal when their product is anti-commutative.
1578
+
1579
+ Parameters
1580
+ ==========
1581
+
1582
+ other : a Quaternion
1583
+
1584
+ Returns
1585
+ =======
1586
+
1587
+ True : if the two pure quaternions seen as 3D vectors are orthogonal.
1588
+ False : if the two pure quaternions seen as 3D vectors are not orthogonal.
1589
+ None : if the two pure quaternions seen as 3D vectors are orthogonal is unknown.
1590
+
1591
+ Examples
1592
+ ========
1593
+
1594
+ >>> from sympy.algebras.quaternion import Quaternion
1595
+ >>> q = Quaternion(0, 4, 4, 4)
1596
+ >>> q1 = Quaternion(0, 8, 8, 8)
1597
+ >>> q.orthogonal(q1)
1598
+ False
1599
+
1600
+ >>> q1 = Quaternion(0, 2, 2, 0)
1601
+ >>> q = Quaternion(0, 2, -2, 0)
1602
+ >>> q.orthogonal(q1)
1603
+ True
1604
+
1605
+ """
1606
+
1607
+ if fuzzy_not(self.is_pure()) or fuzzy_not(other.is_pure()):
1608
+ raise ValueError('The given quaternions must be pure')
1609
+
1610
+ return (self*other + other*self).is_zero_quaternion()
1611
+
1612
+ def index_vector(self):
1613
+ r"""
1614
+ Returns the index vector of the quaternion.
1615
+
1616
+ Explanation
1617
+ ===========
1618
+
1619
+ The index vector is given by $\mathbf{T}(q)$, the norm (or magnitude) of
1620
+ the quaternion $q$, multiplied by $\mathbf{Ax}(q)$, the axis of $q$.
1621
+
1622
+ Returns
1623
+ =======
1624
+
1625
+ Quaternion: representing index vector of the provided quaternion.
1626
+
1627
+ Examples
1628
+ ========
1629
+
1630
+ >>> from sympy.algebras.quaternion import Quaternion
1631
+ >>> q = Quaternion(2, 4, 2, 4)
1632
+ >>> q.index_vector()
1633
+ 0 + 4*sqrt(10)/3*i + 2*sqrt(10)/3*j + 4*sqrt(10)/3*k
1634
+
1635
+ See Also
1636
+ ========
1637
+
1638
+ axis
1639
+ norm
1640
+
1641
+ """
1642
+
1643
+ return self.norm() * self.axis()
1644
+
1645
+ def mensor(self):
1646
+ """
1647
+ Returns the natural logarithm of the norm(magnitude) of the quaternion.
1648
+
1649
+ Examples
1650
+ ========
1651
+
1652
+ >>> from sympy.algebras.quaternion import Quaternion
1653
+ >>> q = Quaternion(2, 4, 2, 4)
1654
+ >>> q.mensor()
1655
+ log(2*sqrt(10))
1656
+ >>> q.norm()
1657
+ 2*sqrt(10)
1658
+
1659
+ See Also
1660
+ ========
1661
+
1662
+ norm
1663
+
1664
+ """
1665
+
1666
+ return ln(self.norm())
.venv/lib/python3.13/site-packages/sympy/assumptions/__init__.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ A module to implement logical predicates and assumption system.
3
+ """
4
+
5
+ from .assume import (
6
+ AppliedPredicate, Predicate, AssumptionsContext, assuming,
7
+ global_assumptions
8
+ )
9
+ from .ask import Q, ask, register_handler, remove_handler
10
+ from .refine import refine
11
+ from .relation import BinaryRelation, AppliedBinaryRelation
12
+
13
+ __all__ = [
14
+ 'AppliedPredicate', 'Predicate', 'AssumptionsContext', 'assuming',
15
+ 'global_assumptions', 'Q', 'ask', 'register_handler', 'remove_handler',
16
+ 'refine',
17
+ 'BinaryRelation', 'AppliedBinaryRelation'
18
+ ]
.venv/lib/python3.13/site-packages/sympy/assumptions/ask.py ADDED
@@ -0,0 +1,651 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Module for querying SymPy objects about assumptions."""
2
+
3
+ from sympy.assumptions.assume import (global_assumptions, Predicate,
4
+ AppliedPredicate)
5
+ from sympy.assumptions.cnf import CNF, EncodedCNF, Literal
6
+ from sympy.core import sympify
7
+ from sympy.core.kind import BooleanKind
8
+ from sympy.core.relational import Eq, Ne, Gt, Lt, Ge, Le
9
+ from sympy.logic.inference import satisfiable
10
+ from sympy.utilities.decorator import memoize_property
11
+ from sympy.utilities.exceptions import (sympy_deprecation_warning,
12
+ SymPyDeprecationWarning,
13
+ ignore_warnings)
14
+
15
+
16
+ # Memoization is necessary for the properties of AssumptionKeys to
17
+ # ensure that only one object of Predicate objects are created.
18
+ # This is because assumption handlers are registered on those objects.
19
+
20
+
21
+ class AssumptionKeys:
22
+ """
23
+ This class contains all the supported keys by ``ask``.
24
+ It should be accessed via the instance ``sympy.Q``.
25
+
26
+ """
27
+
28
+ # DO NOT add methods or properties other than predicate keys.
29
+ # SAT solver checks the properties of Q and use them to compute the
30
+ # fact system. Non-predicate attributes will break this.
31
+
32
+ @memoize_property
33
+ def hermitian(self):
34
+ from .handlers.sets import HermitianPredicate
35
+ return HermitianPredicate()
36
+
37
+ @memoize_property
38
+ def antihermitian(self):
39
+ from .handlers.sets import AntihermitianPredicate
40
+ return AntihermitianPredicate()
41
+
42
+ @memoize_property
43
+ def real(self):
44
+ from .handlers.sets import RealPredicate
45
+ return RealPredicate()
46
+
47
+ @memoize_property
48
+ def extended_real(self):
49
+ from .handlers.sets import ExtendedRealPredicate
50
+ return ExtendedRealPredicate()
51
+
52
+ @memoize_property
53
+ def imaginary(self):
54
+ from .handlers.sets import ImaginaryPredicate
55
+ return ImaginaryPredicate()
56
+
57
+ @memoize_property
58
+ def complex(self):
59
+ from .handlers.sets import ComplexPredicate
60
+ return ComplexPredicate()
61
+
62
+ @memoize_property
63
+ def algebraic(self):
64
+ from .handlers.sets import AlgebraicPredicate
65
+ return AlgebraicPredicate()
66
+
67
+ @memoize_property
68
+ def transcendental(self):
69
+ from .predicates.sets import TranscendentalPredicate
70
+ return TranscendentalPredicate()
71
+
72
+ @memoize_property
73
+ def integer(self):
74
+ from .handlers.sets import IntegerPredicate
75
+ return IntegerPredicate()
76
+
77
+ @memoize_property
78
+ def noninteger(self):
79
+ from .predicates.sets import NonIntegerPredicate
80
+ return NonIntegerPredicate()
81
+
82
+ @memoize_property
83
+ def rational(self):
84
+ from .handlers.sets import RationalPredicate
85
+ return RationalPredicate()
86
+
87
+ @memoize_property
88
+ def irrational(self):
89
+ from .handlers.sets import IrrationalPredicate
90
+ return IrrationalPredicate()
91
+
92
+ @memoize_property
93
+ def finite(self):
94
+ from .handlers.calculus import FinitePredicate
95
+ return FinitePredicate()
96
+
97
+ @memoize_property
98
+ def infinite(self):
99
+ from .handlers.calculus import InfinitePredicate
100
+ return InfinitePredicate()
101
+
102
+ @memoize_property
103
+ def positive_infinite(self):
104
+ from .handlers.calculus import PositiveInfinitePredicate
105
+ return PositiveInfinitePredicate()
106
+
107
+ @memoize_property
108
+ def negative_infinite(self):
109
+ from .handlers.calculus import NegativeInfinitePredicate
110
+ return NegativeInfinitePredicate()
111
+
112
+ @memoize_property
113
+ def positive(self):
114
+ from .handlers.order import PositivePredicate
115
+ return PositivePredicate()
116
+
117
+ @memoize_property
118
+ def negative(self):
119
+ from .handlers.order import NegativePredicate
120
+ return NegativePredicate()
121
+
122
+ @memoize_property
123
+ def zero(self):
124
+ from .handlers.order import ZeroPredicate
125
+ return ZeroPredicate()
126
+
127
+ @memoize_property
128
+ def extended_positive(self):
129
+ from .handlers.order import ExtendedPositivePredicate
130
+ return ExtendedPositivePredicate()
131
+
132
+ @memoize_property
133
+ def extended_negative(self):
134
+ from .handlers.order import ExtendedNegativePredicate
135
+ return ExtendedNegativePredicate()
136
+
137
+ @memoize_property
138
+ def nonzero(self):
139
+ from .handlers.order import NonZeroPredicate
140
+ return NonZeroPredicate()
141
+
142
+ @memoize_property
143
+ def nonpositive(self):
144
+ from .handlers.order import NonPositivePredicate
145
+ return NonPositivePredicate()
146
+
147
+ @memoize_property
148
+ def nonnegative(self):
149
+ from .handlers.order import NonNegativePredicate
150
+ return NonNegativePredicate()
151
+
152
+ @memoize_property
153
+ def extended_nonzero(self):
154
+ from .handlers.order import ExtendedNonZeroPredicate
155
+ return ExtendedNonZeroPredicate()
156
+
157
+ @memoize_property
158
+ def extended_nonpositive(self):
159
+ from .handlers.order import ExtendedNonPositivePredicate
160
+ return ExtendedNonPositivePredicate()
161
+
162
+ @memoize_property
163
+ def extended_nonnegative(self):
164
+ from .handlers.order import ExtendedNonNegativePredicate
165
+ return ExtendedNonNegativePredicate()
166
+
167
+ @memoize_property
168
+ def even(self):
169
+ from .handlers.ntheory import EvenPredicate
170
+ return EvenPredicate()
171
+
172
+ @memoize_property
173
+ def odd(self):
174
+ from .handlers.ntheory import OddPredicate
175
+ return OddPredicate()
176
+
177
+ @memoize_property
178
+ def prime(self):
179
+ from .handlers.ntheory import PrimePredicate
180
+ return PrimePredicate()
181
+
182
+ @memoize_property
183
+ def composite(self):
184
+ from .handlers.ntheory import CompositePredicate
185
+ return CompositePredicate()
186
+
187
+ @memoize_property
188
+ def commutative(self):
189
+ from .handlers.common import CommutativePredicate
190
+ return CommutativePredicate()
191
+
192
+ @memoize_property
193
+ def is_true(self):
194
+ from .handlers.common import IsTruePredicate
195
+ return IsTruePredicate()
196
+
197
+ @memoize_property
198
+ def symmetric(self):
199
+ from .handlers.matrices import SymmetricPredicate
200
+ return SymmetricPredicate()
201
+
202
+ @memoize_property
203
+ def invertible(self):
204
+ from .handlers.matrices import InvertiblePredicate
205
+ return InvertiblePredicate()
206
+
207
+ @memoize_property
208
+ def orthogonal(self):
209
+ from .handlers.matrices import OrthogonalPredicate
210
+ return OrthogonalPredicate()
211
+
212
+ @memoize_property
213
+ def unitary(self):
214
+ from .handlers.matrices import UnitaryPredicate
215
+ return UnitaryPredicate()
216
+
217
+ @memoize_property
218
+ def positive_definite(self):
219
+ from .handlers.matrices import PositiveDefinitePredicate
220
+ return PositiveDefinitePredicate()
221
+
222
+ @memoize_property
223
+ def upper_triangular(self):
224
+ from .handlers.matrices import UpperTriangularPredicate
225
+ return UpperTriangularPredicate()
226
+
227
+ @memoize_property
228
+ def lower_triangular(self):
229
+ from .handlers.matrices import LowerTriangularPredicate
230
+ return LowerTriangularPredicate()
231
+
232
+ @memoize_property
233
+ def diagonal(self):
234
+ from .handlers.matrices import DiagonalPredicate
235
+ return DiagonalPredicate()
236
+
237
+ @memoize_property
238
+ def fullrank(self):
239
+ from .handlers.matrices import FullRankPredicate
240
+ return FullRankPredicate()
241
+
242
+ @memoize_property
243
+ def square(self):
244
+ from .handlers.matrices import SquarePredicate
245
+ return SquarePredicate()
246
+
247
+ @memoize_property
248
+ def integer_elements(self):
249
+ from .handlers.matrices import IntegerElementsPredicate
250
+ return IntegerElementsPredicate()
251
+
252
+ @memoize_property
253
+ def real_elements(self):
254
+ from .handlers.matrices import RealElementsPredicate
255
+ return RealElementsPredicate()
256
+
257
+ @memoize_property
258
+ def complex_elements(self):
259
+ from .handlers.matrices import ComplexElementsPredicate
260
+ return ComplexElementsPredicate()
261
+
262
+ @memoize_property
263
+ def singular(self):
264
+ from .predicates.matrices import SingularPredicate
265
+ return SingularPredicate()
266
+
267
+ @memoize_property
268
+ def normal(self):
269
+ from .predicates.matrices import NormalPredicate
270
+ return NormalPredicate()
271
+
272
+ @memoize_property
273
+ def triangular(self):
274
+ from .predicates.matrices import TriangularPredicate
275
+ return TriangularPredicate()
276
+
277
+ @memoize_property
278
+ def unit_triangular(self):
279
+ from .predicates.matrices import UnitTriangularPredicate
280
+ return UnitTriangularPredicate()
281
+
282
+ @memoize_property
283
+ def eq(self):
284
+ from .relation.equality import EqualityPredicate
285
+ return EqualityPredicate()
286
+
287
+ @memoize_property
288
+ def ne(self):
289
+ from .relation.equality import UnequalityPredicate
290
+ return UnequalityPredicate()
291
+
292
+ @memoize_property
293
+ def gt(self):
294
+ from .relation.equality import StrictGreaterThanPredicate
295
+ return StrictGreaterThanPredicate()
296
+
297
+ @memoize_property
298
+ def ge(self):
299
+ from .relation.equality import GreaterThanPredicate
300
+ return GreaterThanPredicate()
301
+
302
+ @memoize_property
303
+ def lt(self):
304
+ from .relation.equality import StrictLessThanPredicate
305
+ return StrictLessThanPredicate()
306
+
307
+ @memoize_property
308
+ def le(self):
309
+ from .relation.equality import LessThanPredicate
310
+ return LessThanPredicate()
311
+
312
+
313
+ Q = AssumptionKeys()
314
+
315
+ def _extract_all_facts(assump, exprs):
316
+ """
317
+ Extract all relevant assumptions from *assump* with respect to given *exprs*.
318
+
319
+ Parameters
320
+ ==========
321
+
322
+ assump : sympy.assumptions.cnf.CNF
323
+
324
+ exprs : tuple of expressions
325
+
326
+ Returns
327
+ =======
328
+
329
+ sympy.assumptions.cnf.CNF
330
+
331
+ Examples
332
+ ========
333
+
334
+ >>> from sympy import Q
335
+ >>> from sympy.assumptions.cnf import CNF
336
+ >>> from sympy.assumptions.ask import _extract_all_facts
337
+ >>> from sympy.abc import x, y
338
+ >>> assump = CNF.from_prop(Q.positive(x) & Q.integer(y))
339
+ >>> exprs = (x,)
340
+ >>> cnf = _extract_all_facts(assump, exprs)
341
+ >>> cnf.clauses
342
+ {frozenset({Literal(Q.positive, False)})}
343
+
344
+ """
345
+ facts = set()
346
+
347
+ for clause in assump.clauses:
348
+ args = []
349
+ for literal in clause:
350
+ if isinstance(literal.lit, AppliedPredicate) and len(literal.lit.arguments) == 1:
351
+ if literal.lit.arg in exprs:
352
+ # Add literal if it has matching in it
353
+ args.append(Literal(literal.lit.function, literal.is_Not))
354
+ else:
355
+ # If any of the literals doesn't have matching expr don't add the whole clause.
356
+ break
357
+ else:
358
+ # If any of the literals aren't unary predicate don't add the whole clause.
359
+ break
360
+
361
+ else:
362
+ if args:
363
+ facts.add(frozenset(args))
364
+ return CNF(facts)
365
+
366
+
367
+ def ask(proposition, assumptions=True, context=global_assumptions):
368
+ """
369
+ Function to evaluate the proposition with assumptions.
370
+
371
+ Explanation
372
+ ===========
373
+
374
+ This function evaluates the proposition to ``True`` or ``False`` if
375
+ the truth value can be determined. If not, it returns ``None``.
376
+
377
+ It should be discerned from :func:`~.refine` which, when applied to a
378
+ proposition, simplifies the argument to symbolic ``Boolean`` instead of
379
+ Python built-in ``True``, ``False`` or ``None``.
380
+
381
+ **Syntax**
382
+
383
+ * ask(proposition)
384
+ Evaluate the *proposition* in global assumption context.
385
+
386
+ * ask(proposition, assumptions)
387
+ Evaluate the *proposition* with respect to *assumptions* in
388
+ global assumption context.
389
+
390
+ Parameters
391
+ ==========
392
+
393
+ proposition : Boolean
394
+ Proposition which will be evaluated to boolean value. If this is
395
+ not ``AppliedPredicate``, it will be wrapped by ``Q.is_true``.
396
+
397
+ assumptions : Boolean, optional
398
+ Local assumptions to evaluate the *proposition*.
399
+
400
+ context : AssumptionsContext, optional
401
+ Default assumptions to evaluate the *proposition*. By default,
402
+ this is ``sympy.assumptions.global_assumptions`` variable.
403
+
404
+ Returns
405
+ =======
406
+
407
+ ``True``, ``False``, or ``None``
408
+
409
+ Raises
410
+ ======
411
+
412
+ TypeError : *proposition* or *assumptions* is not valid logical expression.
413
+
414
+ ValueError : assumptions are inconsistent.
415
+
416
+ Examples
417
+ ========
418
+
419
+ >>> from sympy import ask, Q, pi
420
+ >>> from sympy.abc import x, y
421
+ >>> ask(Q.rational(pi))
422
+ False
423
+ >>> ask(Q.even(x*y), Q.even(x) & Q.integer(y))
424
+ True
425
+ >>> ask(Q.prime(4*x), Q.integer(x))
426
+ False
427
+
428
+ If the truth value cannot be determined, ``None`` will be returned.
429
+
430
+ >>> print(ask(Q.odd(3*x))) # cannot determine unless we know x
431
+ None
432
+
433
+ ``ValueError`` is raised if assumptions are inconsistent.
434
+
435
+ >>> ask(Q.integer(x), Q.even(x) & Q.odd(x))
436
+ Traceback (most recent call last):
437
+ ...
438
+ ValueError: inconsistent assumptions Q.even(x) & Q.odd(x)
439
+
440
+ Notes
441
+ =====
442
+
443
+ Relations in assumptions are not implemented (yet), so the following
444
+ will not give a meaningful result.
445
+
446
+ >>> ask(Q.positive(x), x > 0)
447
+
448
+ It is however a work in progress.
449
+
450
+ See Also
451
+ ========
452
+
453
+ sympy.assumptions.refine.refine : Simplification using assumptions.
454
+ Proposition is not reduced to ``None`` if the truth value cannot
455
+ be determined.
456
+ """
457
+ from sympy.assumptions.satask import satask
458
+ from sympy.assumptions.lra_satask import lra_satask
459
+ from sympy.logic.algorithms.lra_theory import UnhandledInput
460
+
461
+ proposition = sympify(proposition)
462
+ assumptions = sympify(assumptions)
463
+
464
+ if isinstance(proposition, Predicate) or proposition.kind is not BooleanKind:
465
+ raise TypeError("proposition must be a valid logical expression")
466
+
467
+ if isinstance(assumptions, Predicate) or assumptions.kind is not BooleanKind:
468
+ raise TypeError("assumptions must be a valid logical expression")
469
+
470
+ binrelpreds = {Eq: Q.eq, Ne: Q.ne, Gt: Q.gt, Lt: Q.lt, Ge: Q.ge, Le: Q.le}
471
+ if isinstance(proposition, AppliedPredicate):
472
+ key, args = proposition.function, proposition.arguments
473
+ elif proposition.func in binrelpreds:
474
+ key, args = binrelpreds[type(proposition)], proposition.args
475
+ else:
476
+ key, args = Q.is_true, (proposition,)
477
+
478
+ # convert local and global assumptions to CNF
479
+ assump_cnf = CNF.from_prop(assumptions)
480
+ assump_cnf.extend(context)
481
+
482
+ # extract the relevant facts from assumptions with respect to args
483
+ local_facts = _extract_all_facts(assump_cnf, args)
484
+
485
+ # convert default facts and assumed facts to encoded CNF
486
+ known_facts_cnf = get_all_known_facts()
487
+ enc_cnf = EncodedCNF()
488
+ enc_cnf.from_cnf(CNF(known_facts_cnf))
489
+ enc_cnf.add_from_cnf(local_facts)
490
+
491
+ # check the satisfiability of given assumptions
492
+ if local_facts.clauses and satisfiable(enc_cnf) is False:
493
+ raise ValueError("inconsistent assumptions %s" % assumptions)
494
+
495
+ # quick computation for single fact
496
+ res = _ask_single_fact(key, local_facts)
497
+ if res is not None:
498
+ return res
499
+
500
+ # direct resolution method, no logic
501
+ res = key(*args)._eval_ask(assumptions)
502
+ if res is not None:
503
+ return bool(res)
504
+
505
+ # using satask (still costly)
506
+ res = satask(proposition, assumptions=assumptions, context=context)
507
+ if res is not None:
508
+ return res
509
+
510
+ try:
511
+ res = lra_satask(proposition, assumptions=assumptions, context=context)
512
+ except UnhandledInput:
513
+ return None
514
+
515
+ return res
516
+
517
+
518
+ def _ask_single_fact(key, local_facts):
519
+ """
520
+ Compute the truth value of single predicate using assumptions.
521
+
522
+ Parameters
523
+ ==========
524
+
525
+ key : sympy.assumptions.assume.Predicate
526
+ Proposition predicate.
527
+
528
+ local_facts : sympy.assumptions.cnf.CNF
529
+ Local assumption in CNF form.
530
+
531
+ Returns
532
+ =======
533
+
534
+ ``True``, ``False`` or ``None``
535
+
536
+ Examples
537
+ ========
538
+
539
+ >>> from sympy import Q
540
+ >>> from sympy.assumptions.cnf import CNF
541
+ >>> from sympy.assumptions.ask import _ask_single_fact
542
+
543
+ If prerequisite of proposition is rejected by the assumption,
544
+ return ``False``.
545
+
546
+ >>> key, assump = Q.zero, ~Q.zero
547
+ >>> local_facts = CNF.from_prop(assump)
548
+ >>> _ask_single_fact(key, local_facts)
549
+ False
550
+ >>> key, assump = Q.zero, ~Q.even
551
+ >>> local_facts = CNF.from_prop(assump)
552
+ >>> _ask_single_fact(key, local_facts)
553
+ False
554
+
555
+ If assumption implies the proposition, return ``True``.
556
+
557
+ >>> key, assump = Q.even, Q.zero
558
+ >>> local_facts = CNF.from_prop(assump)
559
+ >>> _ask_single_fact(key, local_facts)
560
+ True
561
+
562
+ If proposition rejects the assumption, return ``False``.
563
+
564
+ >>> key, assump = Q.even, Q.odd
565
+ >>> local_facts = CNF.from_prop(assump)
566
+ >>> _ask_single_fact(key, local_facts)
567
+ False
568
+ """
569
+ if local_facts.clauses:
570
+
571
+ known_facts_dict = get_known_facts_dict()
572
+
573
+ if len(local_facts.clauses) == 1:
574
+ cl, = local_facts.clauses
575
+ if len(cl) == 1:
576
+ f, = cl
577
+ prop_facts = known_facts_dict.get(key, None)
578
+ prop_req = prop_facts[0] if prop_facts is not None else set()
579
+ if f.is_Not and f.arg in prop_req:
580
+ # the prerequisite of proposition is rejected
581
+ return False
582
+
583
+ for clause in local_facts.clauses:
584
+ if len(clause) == 1:
585
+ f, = clause
586
+ prop_facts = known_facts_dict.get(f.arg, None) if not f.is_Not else None
587
+ if prop_facts is None:
588
+ continue
589
+
590
+ prop_req, prop_rej = prop_facts
591
+ if key in prop_req:
592
+ # assumption implies the proposition
593
+ return True
594
+ elif key in prop_rej:
595
+ # proposition rejects the assumption
596
+ return False
597
+
598
+ return None
599
+
600
+
601
+ def register_handler(key, handler):
602
+ """
603
+ Register a handler in the ask system. key must be a string and handler a
604
+ class inheriting from AskHandler.
605
+
606
+ .. deprecated:: 1.8.
607
+ Use multipledispatch handler instead. See :obj:`~.Predicate`.
608
+
609
+ """
610
+ sympy_deprecation_warning(
611
+ """
612
+ The AskHandler system is deprecated. The register_handler() function
613
+ should be replaced with the multipledispatch handler of Predicate.
614
+ """,
615
+ deprecated_since_version="1.8",
616
+ active_deprecations_target='deprecated-askhandler',
617
+ )
618
+ if isinstance(key, Predicate):
619
+ key = key.name.name
620
+ Qkey = getattr(Q, key, None)
621
+ if Qkey is not None:
622
+ Qkey.add_handler(handler)
623
+ else:
624
+ setattr(Q, key, Predicate(key, handlers=[handler]))
625
+
626
+
627
+ def remove_handler(key, handler):
628
+ """
629
+ Removes a handler from the ask system.
630
+
631
+ .. deprecated:: 1.8.
632
+ Use multipledispatch handler instead. See :obj:`~.Predicate`.
633
+
634
+ """
635
+ sympy_deprecation_warning(
636
+ """
637
+ The AskHandler system is deprecated. The remove_handler() function
638
+ should be replaced with the multipledispatch handler of Predicate.
639
+ """,
640
+ deprecated_since_version="1.8",
641
+ active_deprecations_target='deprecated-askhandler',
642
+ )
643
+ if isinstance(key, Predicate):
644
+ key = key.name.name
645
+ # Don't show the same warning again recursively
646
+ with ignore_warnings(SymPyDeprecationWarning):
647
+ getattr(Q, key).remove_handler(handler)
648
+
649
+
650
+ from sympy.assumptions.ask_generated import (get_all_known_facts,
651
+ get_known_facts_dict)
.venv/lib/python3.13/site-packages/sympy/assumptions/ask_generated.py ADDED
@@ -0,0 +1,352 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Do NOT manually edit this file.
3
+ Instead, run ./bin/ask_update.py.
4
+ """
5
+
6
+ from sympy.assumptions.ask import Q
7
+ from sympy.assumptions.cnf import Literal
8
+ from sympy.core.cache import cacheit
9
+
10
+ @cacheit
11
+ def get_all_known_facts():
12
+ """
13
+ Known facts between unary predicates as CNF clauses.
14
+ """
15
+ return {
16
+ frozenset((Literal(Q.algebraic, False), Literal(Q.imaginary, True), Literal(Q.transcendental, False))),
17
+ frozenset((Literal(Q.algebraic, False), Literal(Q.negative, True), Literal(Q.transcendental, False))),
18
+ frozenset((Literal(Q.algebraic, False), Literal(Q.positive, True), Literal(Q.transcendental, False))),
19
+ frozenset((Literal(Q.algebraic, False), Literal(Q.rational, True))),
20
+ frozenset((Literal(Q.algebraic, False), Literal(Q.transcendental, False), Literal(Q.zero, True))),
21
+ frozenset((Literal(Q.algebraic, True), Literal(Q.finite, False))),
22
+ frozenset((Literal(Q.algebraic, True), Literal(Q.transcendental, True))),
23
+ frozenset((Literal(Q.antihermitian, False), Literal(Q.hermitian, False), Literal(Q.zero, True))),
24
+ frozenset((Literal(Q.antihermitian, False), Literal(Q.imaginary, True))),
25
+ frozenset((Literal(Q.commutative, False), Literal(Q.finite, True))),
26
+ frozenset((Literal(Q.commutative, False), Literal(Q.infinite, True))),
27
+ frozenset((Literal(Q.complex_elements, False), Literal(Q.real_elements, True))),
28
+ frozenset((Literal(Q.composite, False), Literal(Q.even, True), Literal(Q.positive, True), Literal(Q.prime, False))),
29
+ frozenset((Literal(Q.composite, True), Literal(Q.even, False), Literal(Q.odd, False))),
30
+ frozenset((Literal(Q.composite, True), Literal(Q.positive, False))),
31
+ frozenset((Literal(Q.composite, True), Literal(Q.prime, True))),
32
+ frozenset((Literal(Q.diagonal, False), Literal(Q.lower_triangular, True), Literal(Q.upper_triangular, True))),
33
+ frozenset((Literal(Q.diagonal, True), Literal(Q.lower_triangular, False))),
34
+ frozenset((Literal(Q.diagonal, True), Literal(Q.normal, False))),
35
+ frozenset((Literal(Q.diagonal, True), Literal(Q.symmetric, False))),
36
+ frozenset((Literal(Q.diagonal, True), Literal(Q.upper_triangular, False))),
37
+ frozenset((Literal(Q.even, False), Literal(Q.odd, False), Literal(Q.prime, True))),
38
+ frozenset((Literal(Q.even, False), Literal(Q.zero, True))),
39
+ frozenset((Literal(Q.even, True), Literal(Q.odd, True))),
40
+ frozenset((Literal(Q.even, True), Literal(Q.rational, False))),
41
+ frozenset((Literal(Q.finite, False), Literal(Q.transcendental, True))),
42
+ frozenset((Literal(Q.finite, True), Literal(Q.infinite, True))),
43
+ frozenset((Literal(Q.fullrank, False), Literal(Q.invertible, True))),
44
+ frozenset((Literal(Q.fullrank, True), Literal(Q.invertible, False), Literal(Q.square, True))),
45
+ frozenset((Literal(Q.hermitian, False), Literal(Q.negative, True))),
46
+ frozenset((Literal(Q.hermitian, False), Literal(Q.positive, True))),
47
+ frozenset((Literal(Q.hermitian, False), Literal(Q.zero, True))),
48
+ frozenset((Literal(Q.imaginary, True), Literal(Q.negative, True))),
49
+ frozenset((Literal(Q.imaginary, True), Literal(Q.positive, True))),
50
+ frozenset((Literal(Q.imaginary, True), Literal(Q.zero, True))),
51
+ frozenset((Literal(Q.infinite, False), Literal(Q.negative_infinite, True))),
52
+ frozenset((Literal(Q.infinite, False), Literal(Q.positive_infinite, True))),
53
+ frozenset((Literal(Q.integer_elements, True), Literal(Q.real_elements, False))),
54
+ frozenset((Literal(Q.invertible, False), Literal(Q.positive_definite, True))),
55
+ frozenset((Literal(Q.invertible, False), Literal(Q.singular, False))),
56
+ frozenset((Literal(Q.invertible, False), Literal(Q.unitary, True))),
57
+ frozenset((Literal(Q.invertible, True), Literal(Q.singular, True))),
58
+ frozenset((Literal(Q.invertible, True), Literal(Q.square, False))),
59
+ frozenset((Literal(Q.irrational, False), Literal(Q.negative, True), Literal(Q.rational, False))),
60
+ frozenset((Literal(Q.irrational, False), Literal(Q.positive, True), Literal(Q.rational, False))),
61
+ frozenset((Literal(Q.irrational, False), Literal(Q.rational, False), Literal(Q.zero, True))),
62
+ frozenset((Literal(Q.irrational, True), Literal(Q.negative, False), Literal(Q.positive, False), Literal(Q.zero, False))),
63
+ frozenset((Literal(Q.irrational, True), Literal(Q.rational, True))),
64
+ frozenset((Literal(Q.lower_triangular, False), Literal(Q.triangular, True), Literal(Q.upper_triangular, False))),
65
+ frozenset((Literal(Q.lower_triangular, True), Literal(Q.triangular, False))),
66
+ frozenset((Literal(Q.negative, False), Literal(Q.positive, False), Literal(Q.rational, True), Literal(Q.zero, False))),
67
+ frozenset((Literal(Q.negative, True), Literal(Q.negative_infinite, True))),
68
+ frozenset((Literal(Q.negative, True), Literal(Q.positive, True))),
69
+ frozenset((Literal(Q.negative, True), Literal(Q.positive_infinite, True))),
70
+ frozenset((Literal(Q.negative, True), Literal(Q.zero, True))),
71
+ frozenset((Literal(Q.negative_infinite, True), Literal(Q.positive, True))),
72
+ frozenset((Literal(Q.negative_infinite, True), Literal(Q.positive_infinite, True))),
73
+ frozenset((Literal(Q.negative_infinite, True), Literal(Q.zero, True))),
74
+ frozenset((Literal(Q.normal, False), Literal(Q.unitary, True))),
75
+ frozenset((Literal(Q.normal, True), Literal(Q.square, False))),
76
+ frozenset((Literal(Q.odd, True), Literal(Q.rational, False))),
77
+ frozenset((Literal(Q.orthogonal, False), Literal(Q.real_elements, True), Literal(Q.unitary, True))),
78
+ frozenset((Literal(Q.orthogonal, True), Literal(Q.positive_definite, False))),
79
+ frozenset((Literal(Q.orthogonal, True), Literal(Q.unitary, False))),
80
+ frozenset((Literal(Q.positive, False), Literal(Q.prime, True))),
81
+ frozenset((Literal(Q.positive, True), Literal(Q.positive_infinite, True))),
82
+ frozenset((Literal(Q.positive, True), Literal(Q.zero, True))),
83
+ frozenset((Literal(Q.positive_infinite, True), Literal(Q.zero, True))),
84
+ frozenset((Literal(Q.square, False), Literal(Q.symmetric, True))),
85
+ frozenset((Literal(Q.triangular, False), Literal(Q.unit_triangular, True))),
86
+ frozenset((Literal(Q.triangular, False), Literal(Q.upper_triangular, True)))
87
+ }
88
+
89
+ @cacheit
90
+ def get_all_known_matrix_facts():
91
+ """
92
+ Known facts between unary predicates for matrices as CNF clauses.
93
+ """
94
+ return {
95
+ frozenset((Literal(Q.complex_elements, False), Literal(Q.real_elements, True))),
96
+ frozenset((Literal(Q.diagonal, False), Literal(Q.lower_triangular, True), Literal(Q.upper_triangular, True))),
97
+ frozenset((Literal(Q.diagonal, True), Literal(Q.lower_triangular, False))),
98
+ frozenset((Literal(Q.diagonal, True), Literal(Q.normal, False))),
99
+ frozenset((Literal(Q.diagonal, True), Literal(Q.symmetric, False))),
100
+ frozenset((Literal(Q.diagonal, True), Literal(Q.upper_triangular, False))),
101
+ frozenset((Literal(Q.fullrank, False), Literal(Q.invertible, True))),
102
+ frozenset((Literal(Q.fullrank, True), Literal(Q.invertible, False), Literal(Q.square, True))),
103
+ frozenset((Literal(Q.integer_elements, True), Literal(Q.real_elements, False))),
104
+ frozenset((Literal(Q.invertible, False), Literal(Q.positive_definite, True))),
105
+ frozenset((Literal(Q.invertible, False), Literal(Q.singular, False))),
106
+ frozenset((Literal(Q.invertible, False), Literal(Q.unitary, True))),
107
+ frozenset((Literal(Q.invertible, True), Literal(Q.singular, True))),
108
+ frozenset((Literal(Q.invertible, True), Literal(Q.square, False))),
109
+ frozenset((Literal(Q.lower_triangular, False), Literal(Q.triangular, True), Literal(Q.upper_triangular, False))),
110
+ frozenset((Literal(Q.lower_triangular, True), Literal(Q.triangular, False))),
111
+ frozenset((Literal(Q.normal, False), Literal(Q.unitary, True))),
112
+ frozenset((Literal(Q.normal, True), Literal(Q.square, False))),
113
+ frozenset((Literal(Q.orthogonal, False), Literal(Q.real_elements, True), Literal(Q.unitary, True))),
114
+ frozenset((Literal(Q.orthogonal, True), Literal(Q.positive_definite, False))),
115
+ frozenset((Literal(Q.orthogonal, True), Literal(Q.unitary, False))),
116
+ frozenset((Literal(Q.square, False), Literal(Q.symmetric, True))),
117
+ frozenset((Literal(Q.triangular, False), Literal(Q.unit_triangular, True))),
118
+ frozenset((Literal(Q.triangular, False), Literal(Q.upper_triangular, True)))
119
+ }
120
+
121
+ @cacheit
122
+ def get_all_known_number_facts():
123
+ """
124
+ Known facts between unary predicates for numbers as CNF clauses.
125
+ """
126
+ return {
127
+ frozenset((Literal(Q.algebraic, False), Literal(Q.imaginary, True), Literal(Q.transcendental, False))),
128
+ frozenset((Literal(Q.algebraic, False), Literal(Q.negative, True), Literal(Q.transcendental, False))),
129
+ frozenset((Literal(Q.algebraic, False), Literal(Q.positive, True), Literal(Q.transcendental, False))),
130
+ frozenset((Literal(Q.algebraic, False), Literal(Q.rational, True))),
131
+ frozenset((Literal(Q.algebraic, False), Literal(Q.transcendental, False), Literal(Q.zero, True))),
132
+ frozenset((Literal(Q.algebraic, True), Literal(Q.finite, False))),
133
+ frozenset((Literal(Q.algebraic, True), Literal(Q.transcendental, True))),
134
+ frozenset((Literal(Q.antihermitian, False), Literal(Q.hermitian, False), Literal(Q.zero, True))),
135
+ frozenset((Literal(Q.antihermitian, False), Literal(Q.imaginary, True))),
136
+ frozenset((Literal(Q.commutative, False), Literal(Q.finite, True))),
137
+ frozenset((Literal(Q.commutative, False), Literal(Q.infinite, True))),
138
+ frozenset((Literal(Q.composite, False), Literal(Q.even, True), Literal(Q.positive, True), Literal(Q.prime, False))),
139
+ frozenset((Literal(Q.composite, True), Literal(Q.even, False), Literal(Q.odd, False))),
140
+ frozenset((Literal(Q.composite, True), Literal(Q.positive, False))),
141
+ frozenset((Literal(Q.composite, True), Literal(Q.prime, True))),
142
+ frozenset((Literal(Q.even, False), Literal(Q.odd, False), Literal(Q.prime, True))),
143
+ frozenset((Literal(Q.even, False), Literal(Q.zero, True))),
144
+ frozenset((Literal(Q.even, True), Literal(Q.odd, True))),
145
+ frozenset((Literal(Q.even, True), Literal(Q.rational, False))),
146
+ frozenset((Literal(Q.finite, False), Literal(Q.transcendental, True))),
147
+ frozenset((Literal(Q.finite, True), Literal(Q.infinite, True))),
148
+ frozenset((Literal(Q.hermitian, False), Literal(Q.negative, True))),
149
+ frozenset((Literal(Q.hermitian, False), Literal(Q.positive, True))),
150
+ frozenset((Literal(Q.hermitian, False), Literal(Q.zero, True))),
151
+ frozenset((Literal(Q.imaginary, True), Literal(Q.negative, True))),
152
+ frozenset((Literal(Q.imaginary, True), Literal(Q.positive, True))),
153
+ frozenset((Literal(Q.imaginary, True), Literal(Q.zero, True))),
154
+ frozenset((Literal(Q.infinite, False), Literal(Q.negative_infinite, True))),
155
+ frozenset((Literal(Q.infinite, False), Literal(Q.positive_infinite, True))),
156
+ frozenset((Literal(Q.irrational, False), Literal(Q.negative, True), Literal(Q.rational, False))),
157
+ frozenset((Literal(Q.irrational, False), Literal(Q.positive, True), Literal(Q.rational, False))),
158
+ frozenset((Literal(Q.irrational, False), Literal(Q.rational, False), Literal(Q.zero, True))),
159
+ frozenset((Literal(Q.irrational, True), Literal(Q.negative, False), Literal(Q.positive, False), Literal(Q.zero, False))),
160
+ frozenset((Literal(Q.irrational, True), Literal(Q.rational, True))),
161
+ frozenset((Literal(Q.negative, False), Literal(Q.positive, False), Literal(Q.rational, True), Literal(Q.zero, False))),
162
+ frozenset((Literal(Q.negative, True), Literal(Q.negative_infinite, True))),
163
+ frozenset((Literal(Q.negative, True), Literal(Q.positive, True))),
164
+ frozenset((Literal(Q.negative, True), Literal(Q.positive_infinite, True))),
165
+ frozenset((Literal(Q.negative, True), Literal(Q.zero, True))),
166
+ frozenset((Literal(Q.negative_infinite, True), Literal(Q.positive, True))),
167
+ frozenset((Literal(Q.negative_infinite, True), Literal(Q.positive_infinite, True))),
168
+ frozenset((Literal(Q.negative_infinite, True), Literal(Q.zero, True))),
169
+ frozenset((Literal(Q.odd, True), Literal(Q.rational, False))),
170
+ frozenset((Literal(Q.positive, False), Literal(Q.prime, True))),
171
+ frozenset((Literal(Q.positive, True), Literal(Q.positive_infinite, True))),
172
+ frozenset((Literal(Q.positive, True), Literal(Q.zero, True))),
173
+ frozenset((Literal(Q.positive_infinite, True), Literal(Q.zero, True)))
174
+ }
175
+
176
+ @cacheit
177
+ def get_known_facts_dict():
178
+ """
179
+ Logical relations between unary predicates as dictionary.
180
+
181
+ Each key is a predicate, and item is two groups of predicates.
182
+ First group contains the predicates which are implied by the key, and
183
+ second group contains the predicates which are rejected by the key.
184
+
185
+ """
186
+ return {
187
+ Q.algebraic: (set([Q.algebraic, Q.commutative, Q.complex, Q.finite]),
188
+ set([Q.infinite, Q.negative_infinite, Q.positive_infinite,
189
+ Q.transcendental])),
190
+ Q.antihermitian: (set([Q.antihermitian]), set([])),
191
+ Q.commutative: (set([Q.commutative]), set([])),
192
+ Q.complex: (set([Q.commutative, Q.complex, Q.finite]),
193
+ set([Q.infinite, Q.negative_infinite, Q.positive_infinite])),
194
+ Q.complex_elements: (set([Q.complex_elements]), set([])),
195
+ Q.composite: (set([Q.algebraic, Q.commutative, Q.complex, Q.composite,
196
+ Q.extended_nonnegative, Q.extended_nonzero,
197
+ Q.extended_positive, Q.extended_real, Q.finite, Q.hermitian,
198
+ Q.integer, Q.nonnegative, Q.nonzero, Q.positive, Q.rational,
199
+ Q.real]), set([Q.extended_negative, Q.extended_nonpositive,
200
+ Q.imaginary, Q.infinite, Q.irrational, Q.negative,
201
+ Q.negative_infinite, Q.nonpositive, Q.positive_infinite,
202
+ Q.prime, Q.transcendental, Q.zero])),
203
+ Q.diagonal: (set([Q.diagonal, Q.lower_triangular, Q.normal, Q.square,
204
+ Q.symmetric, Q.triangular, Q.upper_triangular]), set([])),
205
+ Q.even: (set([Q.algebraic, Q.commutative, Q.complex, Q.even,
206
+ Q.extended_real, Q.finite, Q.hermitian, Q.integer, Q.rational,
207
+ Q.real]), set([Q.imaginary, Q.infinite, Q.irrational,
208
+ Q.negative_infinite, Q.odd, Q.positive_infinite,
209
+ Q.transcendental])),
210
+ Q.extended_negative: (set([Q.commutative, Q.extended_negative,
211
+ Q.extended_nonpositive, Q.extended_nonzero, Q.extended_real]),
212
+ set([Q.composite, Q.extended_nonnegative, Q.extended_positive,
213
+ Q.imaginary, Q.nonnegative, Q.positive, Q.positive_infinite,
214
+ Q.prime, Q.zero])),
215
+ Q.extended_nonnegative: (set([Q.commutative, Q.extended_nonnegative,
216
+ Q.extended_real]), set([Q.extended_negative, Q.imaginary,
217
+ Q.negative, Q.negative_infinite])),
218
+ Q.extended_nonpositive: (set([Q.commutative, Q.extended_nonpositive,
219
+ Q.extended_real]), set([Q.composite, Q.extended_positive,
220
+ Q.imaginary, Q.positive, Q.positive_infinite, Q.prime])),
221
+ Q.extended_nonzero: (set([Q.commutative, Q.extended_nonzero,
222
+ Q.extended_real]), set([Q.imaginary, Q.zero])),
223
+ Q.extended_positive: (set([Q.commutative, Q.extended_nonnegative,
224
+ Q.extended_nonzero, Q.extended_positive, Q.extended_real]),
225
+ set([Q.extended_negative, Q.extended_nonpositive, Q.imaginary,
226
+ Q.negative, Q.negative_infinite, Q.nonpositive, Q.zero])),
227
+ Q.extended_real: (set([Q.commutative, Q.extended_real]),
228
+ set([Q.imaginary])),
229
+ Q.finite: (set([Q.commutative, Q.finite]), set([Q.infinite,
230
+ Q.negative_infinite, Q.positive_infinite])),
231
+ Q.fullrank: (set([Q.fullrank]), set([])),
232
+ Q.hermitian: (set([Q.hermitian]), set([])),
233
+ Q.imaginary: (set([Q.antihermitian, Q.commutative, Q.complex,
234
+ Q.finite, Q.imaginary]), set([Q.composite, Q.even,
235
+ Q.extended_negative, Q.extended_nonnegative,
236
+ Q.extended_nonpositive, Q.extended_nonzero,
237
+ Q.extended_positive, Q.extended_real, Q.infinite, Q.integer,
238
+ Q.irrational, Q.negative, Q.negative_infinite, Q.nonnegative,
239
+ Q.nonpositive, Q.nonzero, Q.odd, Q.positive,
240
+ Q.positive_infinite, Q.prime, Q.rational, Q.real, Q.zero])),
241
+ Q.infinite: (set([Q.commutative, Q.infinite]), set([Q.algebraic,
242
+ Q.complex, Q.composite, Q.even, Q.finite, Q.imaginary,
243
+ Q.integer, Q.irrational, Q.negative, Q.nonnegative,
244
+ Q.nonpositive, Q.nonzero, Q.odd, Q.positive, Q.prime,
245
+ Q.rational, Q.real, Q.transcendental, Q.zero])),
246
+ Q.integer: (set([Q.algebraic, Q.commutative, Q.complex,
247
+ Q.extended_real, Q.finite, Q.hermitian, Q.integer, Q.rational,
248
+ Q.real]), set([Q.imaginary, Q.infinite, Q.irrational,
249
+ Q.negative_infinite, Q.positive_infinite, Q.transcendental])),
250
+ Q.integer_elements: (set([Q.complex_elements, Q.integer_elements,
251
+ Q.real_elements]), set([])),
252
+ Q.invertible: (set([Q.fullrank, Q.invertible, Q.square]),
253
+ set([Q.singular])),
254
+ Q.irrational: (set([Q.commutative, Q.complex, Q.extended_nonzero,
255
+ Q.extended_real, Q.finite, Q.hermitian, Q.irrational,
256
+ Q.nonzero, Q.real]), set([Q.composite, Q.even, Q.imaginary,
257
+ Q.infinite, Q.integer, Q.negative_infinite, Q.odd,
258
+ Q.positive_infinite, Q.prime, Q.rational, Q.zero])),
259
+ Q.is_true: (set([Q.is_true]), set([])),
260
+ Q.lower_triangular: (set([Q.lower_triangular, Q.triangular]), set([])),
261
+ Q.negative: (set([Q.commutative, Q.complex, Q.extended_negative,
262
+ Q.extended_nonpositive, Q.extended_nonzero, Q.extended_real,
263
+ Q.finite, Q.hermitian, Q.negative, Q.nonpositive, Q.nonzero,
264
+ Q.real]), set([Q.composite, Q.extended_nonnegative,
265
+ Q.extended_positive, Q.imaginary, Q.infinite,
266
+ Q.negative_infinite, Q.nonnegative, Q.positive,
267
+ Q.positive_infinite, Q.prime, Q.zero])),
268
+ Q.negative_infinite: (set([Q.commutative, Q.extended_negative,
269
+ Q.extended_nonpositive, Q.extended_nonzero, Q.extended_real,
270
+ Q.infinite, Q.negative_infinite]), set([Q.algebraic,
271
+ Q.complex, Q.composite, Q.even, Q.extended_nonnegative,
272
+ Q.extended_positive, Q.finite, Q.imaginary, Q.integer,
273
+ Q.irrational, Q.negative, Q.nonnegative, Q.nonpositive,
274
+ Q.nonzero, Q.odd, Q.positive, Q.positive_infinite, Q.prime,
275
+ Q.rational, Q.real, Q.transcendental, Q.zero])),
276
+ Q.noninteger: (set([Q.noninteger]), set([])),
277
+ Q.nonnegative: (set([Q.commutative, Q.complex, Q.extended_nonnegative,
278
+ Q.extended_real, Q.finite, Q.hermitian, Q.nonnegative,
279
+ Q.real]), set([Q.extended_negative, Q.imaginary, Q.infinite,
280
+ Q.negative, Q.negative_infinite, Q.positive_infinite])),
281
+ Q.nonpositive: (set([Q.commutative, Q.complex, Q.extended_nonpositive,
282
+ Q.extended_real, Q.finite, Q.hermitian, Q.nonpositive,
283
+ Q.real]), set([Q.composite, Q.extended_positive, Q.imaginary,
284
+ Q.infinite, Q.negative_infinite, Q.positive,
285
+ Q.positive_infinite, Q.prime])),
286
+ Q.nonzero: (set([Q.commutative, Q.complex, Q.extended_nonzero,
287
+ Q.extended_real, Q.finite, Q.hermitian, Q.nonzero, Q.real]),
288
+ set([Q.imaginary, Q.infinite, Q.negative_infinite,
289
+ Q.positive_infinite, Q.zero])),
290
+ Q.normal: (set([Q.normal, Q.square]), set([])),
291
+ Q.odd: (set([Q.algebraic, Q.commutative, Q.complex,
292
+ Q.extended_nonzero, Q.extended_real, Q.finite, Q.hermitian,
293
+ Q.integer, Q.nonzero, Q.odd, Q.rational, Q.real]),
294
+ set([Q.even, Q.imaginary, Q.infinite, Q.irrational,
295
+ Q.negative_infinite, Q.positive_infinite, Q.transcendental,
296
+ Q.zero])),
297
+ Q.orthogonal: (set([Q.fullrank, Q.invertible, Q.normal, Q.orthogonal,
298
+ Q.positive_definite, Q.square, Q.unitary]), set([Q.singular])),
299
+ Q.positive: (set([Q.commutative, Q.complex, Q.extended_nonnegative,
300
+ Q.extended_nonzero, Q.extended_positive, Q.extended_real,
301
+ Q.finite, Q.hermitian, Q.nonnegative, Q.nonzero, Q.positive,
302
+ Q.real]), set([Q.extended_negative, Q.extended_nonpositive,
303
+ Q.imaginary, Q.infinite, Q.negative, Q.negative_infinite,
304
+ Q.nonpositive, Q.positive_infinite, Q.zero])),
305
+ Q.positive_definite: (set([Q.fullrank, Q.invertible,
306
+ Q.positive_definite, Q.square]), set([Q.singular])),
307
+ Q.positive_infinite: (set([Q.commutative, Q.extended_nonnegative,
308
+ Q.extended_nonzero, Q.extended_positive, Q.extended_real,
309
+ Q.infinite, Q.positive_infinite]), set([Q.algebraic,
310
+ Q.complex, Q.composite, Q.even, Q.extended_negative,
311
+ Q.extended_nonpositive, Q.finite, Q.imaginary, Q.integer,
312
+ Q.irrational, Q.negative, Q.negative_infinite, Q.nonnegative,
313
+ Q.nonpositive, Q.nonzero, Q.odd, Q.positive, Q.prime,
314
+ Q.rational, Q.real, Q.transcendental, Q.zero])),
315
+ Q.prime: (set([Q.algebraic, Q.commutative, Q.complex,
316
+ Q.extended_nonnegative, Q.extended_nonzero,
317
+ Q.extended_positive, Q.extended_real, Q.finite, Q.hermitian,
318
+ Q.integer, Q.nonnegative, Q.nonzero, Q.positive, Q.prime,
319
+ Q.rational, Q.real]), set([Q.composite, Q.extended_negative,
320
+ Q.extended_nonpositive, Q.imaginary, Q.infinite, Q.irrational,
321
+ Q.negative, Q.negative_infinite, Q.nonpositive,
322
+ Q.positive_infinite, Q.transcendental, Q.zero])),
323
+ Q.rational: (set([Q.algebraic, Q.commutative, Q.complex,
324
+ Q.extended_real, Q.finite, Q.hermitian, Q.rational, Q.real]),
325
+ set([Q.imaginary, Q.infinite, Q.irrational,
326
+ Q.negative_infinite, Q.positive_infinite, Q.transcendental])),
327
+ Q.real: (set([Q.commutative, Q.complex, Q.extended_real, Q.finite,
328
+ Q.hermitian, Q.real]), set([Q.imaginary, Q.infinite,
329
+ Q.negative_infinite, Q.positive_infinite])),
330
+ Q.real_elements: (set([Q.complex_elements, Q.real_elements]), set([])),
331
+ Q.singular: (set([Q.singular]), set([Q.invertible, Q.orthogonal,
332
+ Q.positive_definite, Q.unitary])),
333
+ Q.square: (set([Q.square]), set([])),
334
+ Q.symmetric: (set([Q.square, Q.symmetric]), set([])),
335
+ Q.transcendental: (set([Q.commutative, Q.complex, Q.finite,
336
+ Q.transcendental]), set([Q.algebraic, Q.composite, Q.even,
337
+ Q.infinite, Q.integer, Q.negative_infinite, Q.odd,
338
+ Q.positive_infinite, Q.prime, Q.rational, Q.zero])),
339
+ Q.triangular: (set([Q.triangular]), set([])),
340
+ Q.unit_triangular: (set([Q.triangular, Q.unit_triangular]), set([])),
341
+ Q.unitary: (set([Q.fullrank, Q.invertible, Q.normal, Q.square,
342
+ Q.unitary]), set([Q.singular])),
343
+ Q.upper_triangular: (set([Q.triangular, Q.upper_triangular]), set([])),
344
+ Q.zero: (set([Q.algebraic, Q.commutative, Q.complex, Q.even,
345
+ Q.extended_nonnegative, Q.extended_nonpositive,
346
+ Q.extended_real, Q.finite, Q.hermitian, Q.integer,
347
+ Q.nonnegative, Q.nonpositive, Q.rational, Q.real, Q.zero]),
348
+ set([Q.composite, Q.extended_negative, Q.extended_nonzero,
349
+ Q.extended_positive, Q.imaginary, Q.infinite, Q.irrational,
350
+ Q.negative, Q.negative_infinite, Q.nonzero, Q.odd, Q.positive,
351
+ Q.positive_infinite, Q.prime, Q.transcendental])),
352
+ }
.venv/lib/python3.13/site-packages/sympy/assumptions/assume.py ADDED
@@ -0,0 +1,485 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """A module which implements predicates and assumption context."""
2
+
3
+ from contextlib import contextmanager
4
+ import inspect
5
+ from sympy.core.symbol import Str
6
+ from sympy.core.sympify import _sympify
7
+ from sympy.logic.boolalg import Boolean, false, true
8
+ from sympy.multipledispatch.dispatcher import Dispatcher, str_signature
9
+ from sympy.utilities.exceptions import sympy_deprecation_warning
10
+ from sympy.utilities.iterables import is_sequence
11
+ from sympy.utilities.source import get_class
12
+
13
+
14
+ class AssumptionsContext(set):
15
+ """
16
+ Set containing default assumptions which are applied to the ``ask()``
17
+ function.
18
+
19
+ Explanation
20
+ ===========
21
+
22
+ This is used to represent global assumptions, but you can also use this
23
+ class to create your own local assumptions contexts. It is basically a thin
24
+ wrapper to Python's set, so see its documentation for advanced usage.
25
+
26
+ Examples
27
+ ========
28
+
29
+ The default assumption context is ``global_assumptions``, which is initially empty:
30
+
31
+ >>> from sympy import ask, Q
32
+ >>> from sympy.assumptions import global_assumptions
33
+ >>> global_assumptions
34
+ AssumptionsContext()
35
+
36
+ You can add default assumptions:
37
+
38
+ >>> from sympy.abc import x
39
+ >>> global_assumptions.add(Q.real(x))
40
+ >>> global_assumptions
41
+ AssumptionsContext({Q.real(x)})
42
+ >>> ask(Q.real(x))
43
+ True
44
+
45
+ And remove them:
46
+
47
+ >>> global_assumptions.remove(Q.real(x))
48
+ >>> print(ask(Q.real(x)))
49
+ None
50
+
51
+ The ``clear()`` method removes every assumption:
52
+
53
+ >>> global_assumptions.add(Q.positive(x))
54
+ >>> global_assumptions
55
+ AssumptionsContext({Q.positive(x)})
56
+ >>> global_assumptions.clear()
57
+ >>> global_assumptions
58
+ AssumptionsContext()
59
+
60
+ See Also
61
+ ========
62
+
63
+ assuming
64
+
65
+ """
66
+
67
+ def add(self, *assumptions):
68
+ """Add assumptions."""
69
+ for a in assumptions:
70
+ super().add(a)
71
+
72
+ def _sympystr(self, printer):
73
+ if not self:
74
+ return "%s()" % self.__class__.__name__
75
+ return "{}({})".format(self.__class__.__name__, printer._print_set(self))
76
+
77
+ global_assumptions = AssumptionsContext()
78
+
79
+
80
+ class AppliedPredicate(Boolean):
81
+ """
82
+ The class of expressions resulting from applying ``Predicate`` to
83
+ the arguments. ``AppliedPredicate`` merely wraps its argument and
84
+ remain unevaluated. To evaluate it, use the ``ask()`` function.
85
+
86
+ Examples
87
+ ========
88
+
89
+ >>> from sympy import Q, ask
90
+ >>> Q.integer(1)
91
+ Q.integer(1)
92
+
93
+ The ``function`` attribute returns the predicate, and the ``arguments``
94
+ attribute returns the tuple of arguments.
95
+
96
+ >>> type(Q.integer(1))
97
+ <class 'sympy.assumptions.assume.AppliedPredicate'>
98
+ >>> Q.integer(1).function
99
+ Q.integer
100
+ >>> Q.integer(1).arguments
101
+ (1,)
102
+
103
+ Applied predicates can be evaluated to a boolean value with ``ask``:
104
+
105
+ >>> ask(Q.integer(1))
106
+ True
107
+
108
+ """
109
+ __slots__ = ()
110
+
111
+ def __new__(cls, predicate, *args):
112
+ if not isinstance(predicate, Predicate):
113
+ raise TypeError("%s is not a Predicate." % predicate)
114
+ args = map(_sympify, args)
115
+ return super().__new__(cls, predicate, *args)
116
+
117
+ @property
118
+ def arg(self):
119
+ """
120
+ Return the expression used by this assumption.
121
+
122
+ Examples
123
+ ========
124
+
125
+ >>> from sympy import Q, Symbol
126
+ >>> x = Symbol('x')
127
+ >>> a = Q.integer(x + 1)
128
+ >>> a.arg
129
+ x + 1
130
+
131
+ """
132
+ # Will be deprecated
133
+ args = self._args
134
+ if len(args) == 2:
135
+ # backwards compatibility
136
+ return args[1]
137
+ raise TypeError("'arg' property is allowed only for unary predicates.")
138
+
139
+ @property
140
+ def function(self):
141
+ """
142
+ Return the predicate.
143
+ """
144
+ # Will be changed to self.args[0] after args overriding is removed
145
+ return self._args[0]
146
+
147
+ @property
148
+ def arguments(self):
149
+ """
150
+ Return the arguments which are applied to the predicate.
151
+ """
152
+ # Will be changed to self.args[1:] after args overriding is removed
153
+ return self._args[1:]
154
+
155
+ def _eval_ask(self, assumptions):
156
+ return self.function.eval(self.arguments, assumptions)
157
+
158
+ @property
159
+ def binary_symbols(self):
160
+ from .ask import Q
161
+ if self.function == Q.is_true:
162
+ i = self.arguments[0]
163
+ if i.is_Boolean or i.is_Symbol:
164
+ return i.binary_symbols
165
+ if self.function in (Q.eq, Q.ne):
166
+ if true in self.arguments or false in self.arguments:
167
+ if self.arguments[0].is_Symbol:
168
+ return {self.arguments[0]}
169
+ elif self.arguments[1].is_Symbol:
170
+ return {self.arguments[1]}
171
+ return set()
172
+
173
+
174
+ class PredicateMeta(type):
175
+ def __new__(cls, clsname, bases, dct):
176
+ # If handler is not defined, assign empty dispatcher.
177
+ if "handler" not in dct:
178
+ name = f"Ask{clsname.capitalize()}Handler"
179
+ handler = Dispatcher(name, doc="Handler for key %s" % name)
180
+ dct["handler"] = handler
181
+
182
+ dct["_orig_doc"] = dct.get("__doc__", "")
183
+
184
+ return super().__new__(cls, clsname, bases, dct)
185
+
186
+ @property
187
+ def __doc__(cls):
188
+ handler = cls.handler
189
+ doc = cls._orig_doc
190
+ if cls is not Predicate and handler is not None:
191
+ doc += "Handler\n"
192
+ doc += " =======\n\n"
193
+
194
+ # Append the handler's doc without breaking sphinx documentation.
195
+ docs = [" Multiply dispatched method: %s" % handler.name]
196
+ if handler.doc:
197
+ for line in handler.doc.splitlines():
198
+ if not line:
199
+ continue
200
+ docs.append(" %s" % line)
201
+ other = []
202
+ for sig in handler.ordering[::-1]:
203
+ func = handler.funcs[sig]
204
+ if func.__doc__:
205
+ s = ' Inputs: <%s>' % str_signature(sig)
206
+ lines = []
207
+ for line in func.__doc__.splitlines():
208
+ lines.append(" %s" % line)
209
+ s += "\n".join(lines)
210
+ docs.append(s)
211
+ else:
212
+ other.append(str_signature(sig))
213
+ if other:
214
+ othersig = " Other signatures:"
215
+ for line in other:
216
+ othersig += "\n * %s" % line
217
+ docs.append(othersig)
218
+
219
+ doc += '\n\n'.join(docs)
220
+
221
+ return doc
222
+
223
+
224
+ class Predicate(Boolean, metaclass=PredicateMeta):
225
+ """
226
+ Base class for mathematical predicates. It also serves as a
227
+ constructor for undefined predicate objects.
228
+
229
+ Explanation
230
+ ===========
231
+
232
+ Predicate is a function that returns a boolean value [1].
233
+
234
+ Predicate function is object, and it is instance of predicate class.
235
+ When a predicate is applied to arguments, ``AppliedPredicate``
236
+ instance is returned. This merely wraps the argument and remain
237
+ unevaluated. To obtain the truth value of applied predicate, use the
238
+ function ``ask``.
239
+
240
+ Evaluation of predicate is done by multiple dispatching. You can
241
+ register new handler to the predicate to support new types.
242
+
243
+ Every predicate in SymPy can be accessed via the property of ``Q``.
244
+ For example, ``Q.even`` returns the predicate which checks if the
245
+ argument is even number.
246
+
247
+ To define a predicate which can be evaluated, you must subclass this
248
+ class, make an instance of it, and register it to ``Q``. After then,
249
+ dispatch the handler by argument types.
250
+
251
+ If you directly construct predicate using this class, you will get
252
+ ``UndefinedPredicate`` which cannot be dispatched. This is useful
253
+ when you are building boolean expressions which do not need to be
254
+ evaluated.
255
+
256
+ Examples
257
+ ========
258
+
259
+ Applying and evaluating to boolean value:
260
+
261
+ >>> from sympy import Q, ask
262
+ >>> ask(Q.prime(7))
263
+ True
264
+
265
+ You can define a new predicate by subclassing and dispatching. Here,
266
+ we define a predicate for sexy primes [2] as an example.
267
+
268
+ >>> from sympy import Predicate, Integer
269
+ >>> class SexyPrimePredicate(Predicate):
270
+ ... name = "sexyprime"
271
+ >>> Q.sexyprime = SexyPrimePredicate()
272
+ >>> @Q.sexyprime.register(Integer, Integer)
273
+ ... def _(int1, int2, assumptions):
274
+ ... args = sorted([int1, int2])
275
+ ... if not all(ask(Q.prime(a), assumptions) for a in args):
276
+ ... return False
277
+ ... return args[1] - args[0] == 6
278
+ >>> ask(Q.sexyprime(5, 11))
279
+ True
280
+
281
+ Direct constructing returns ``UndefinedPredicate``, which can be
282
+ applied but cannot be dispatched.
283
+
284
+ >>> from sympy import Predicate, Integer
285
+ >>> Q.P = Predicate("P")
286
+ >>> type(Q.P)
287
+ <class 'sympy.assumptions.assume.UndefinedPredicate'>
288
+ >>> Q.P(1)
289
+ Q.P(1)
290
+ >>> Q.P.register(Integer)(lambda expr, assump: True)
291
+ Traceback (most recent call last):
292
+ ...
293
+ TypeError: <class 'sympy.assumptions.assume.UndefinedPredicate'> cannot be dispatched.
294
+
295
+ References
296
+ ==========
297
+
298
+ .. [1] https://en.wikipedia.org/wiki/Predicate_%28mathematical_logic%29
299
+ .. [2] https://en.wikipedia.org/wiki/Sexy_prime
300
+
301
+ """
302
+
303
+ is_Atom = True
304
+
305
+ def __new__(cls, *args, **kwargs):
306
+ if cls is Predicate:
307
+ return UndefinedPredicate(*args, **kwargs)
308
+ obj = super().__new__(cls, *args)
309
+ return obj
310
+
311
+ @property
312
+ def name(self):
313
+ # May be overridden
314
+ return type(self).__name__
315
+
316
+ @classmethod
317
+ def register(cls, *types, **kwargs):
318
+ """
319
+ Register the signature to the handler.
320
+ """
321
+ if cls.handler is None:
322
+ raise TypeError("%s cannot be dispatched." % type(cls))
323
+ return cls.handler.register(*types, **kwargs)
324
+
325
+ @classmethod
326
+ def register_many(cls, *types, **kwargs):
327
+ """
328
+ Register multiple signatures to same handler.
329
+ """
330
+ def _(func):
331
+ for t in types:
332
+ if not is_sequence(t):
333
+ t = (t,) # for convenience, allow passing `type` to mean `(type,)`
334
+ cls.register(*t, **kwargs)(func)
335
+ return _
336
+
337
+ def __call__(self, *args):
338
+ return AppliedPredicate(self, *args)
339
+
340
+ def eval(self, args, assumptions=True):
341
+ """
342
+ Evaluate ``self(*args)`` under the given assumptions.
343
+
344
+ This uses only direct resolution methods, not logical inference.
345
+ """
346
+ result = None
347
+ try:
348
+ result = self.handler(*args, assumptions=assumptions)
349
+ except NotImplementedError:
350
+ pass
351
+ return result
352
+
353
+ def _eval_refine(self, assumptions):
354
+ # When Predicate is no longer Boolean, delete this method
355
+ return self
356
+
357
+
358
+ class UndefinedPredicate(Predicate):
359
+ """
360
+ Predicate without handler.
361
+
362
+ Explanation
363
+ ===========
364
+
365
+ This predicate is generated by using ``Predicate`` directly for
366
+ construction. It does not have a handler, and evaluating this with
367
+ arguments is done by SAT solver.
368
+
369
+ Examples
370
+ ========
371
+
372
+ >>> from sympy import Predicate, Q
373
+ >>> Q.P = Predicate('P')
374
+ >>> Q.P.func
375
+ <class 'sympy.assumptions.assume.UndefinedPredicate'>
376
+ >>> Q.P.name
377
+ Str('P')
378
+
379
+ """
380
+
381
+ handler = None
382
+
383
+ def __new__(cls, name, handlers=None):
384
+ # "handlers" parameter supports old design
385
+ if not isinstance(name, Str):
386
+ name = Str(name)
387
+ obj = super(Boolean, cls).__new__(cls, name)
388
+ obj.handlers = handlers or []
389
+ return obj
390
+
391
+ @property
392
+ def name(self):
393
+ return self.args[0]
394
+
395
+ def _hashable_content(self):
396
+ return (self.name,)
397
+
398
+ def __getnewargs__(self):
399
+ return (self.name,)
400
+
401
+ def __call__(self, expr):
402
+ return AppliedPredicate(self, expr)
403
+
404
+ def add_handler(self, handler):
405
+ sympy_deprecation_warning(
406
+ """
407
+ The AskHandler system is deprecated. Predicate.add_handler()
408
+ should be replaced with the multipledispatch handler of Predicate.
409
+ """,
410
+ deprecated_since_version="1.8",
411
+ active_deprecations_target='deprecated-askhandler',
412
+ )
413
+ self.handlers.append(handler)
414
+
415
+ def remove_handler(self, handler):
416
+ sympy_deprecation_warning(
417
+ """
418
+ The AskHandler system is deprecated. Predicate.remove_handler()
419
+ should be replaced with the multipledispatch handler of Predicate.
420
+ """,
421
+ deprecated_since_version="1.8",
422
+ active_deprecations_target='deprecated-askhandler',
423
+ )
424
+ self.handlers.remove(handler)
425
+
426
+ def eval(self, args, assumptions=True):
427
+ # Support for deprecated design
428
+ # When old design is removed, this will always return None
429
+ sympy_deprecation_warning(
430
+ """
431
+ The AskHandler system is deprecated. Evaluating UndefinedPredicate
432
+ objects should be replaced with the multipledispatch handler of
433
+ Predicate.
434
+ """,
435
+ deprecated_since_version="1.8",
436
+ active_deprecations_target='deprecated-askhandler',
437
+ stacklevel=5,
438
+ )
439
+ expr, = args
440
+ res, _res = None, None
441
+ mro = inspect.getmro(type(expr))
442
+ for handler in self.handlers:
443
+ cls = get_class(handler)
444
+ for subclass in mro:
445
+ eval_ = getattr(cls, subclass.__name__, None)
446
+ if eval_ is None:
447
+ continue
448
+ res = eval_(expr, assumptions)
449
+ # Do not stop if value returned is None
450
+ # Try to check for higher classes
451
+ if res is None:
452
+ continue
453
+ if _res is None:
454
+ _res = res
455
+ else:
456
+ # only check consistency if both resolutors have concluded
457
+ if _res != res:
458
+ raise ValueError('incompatible resolutors')
459
+ break
460
+ return res
461
+
462
+
463
+ @contextmanager
464
+ def assuming(*assumptions):
465
+ """
466
+ Context manager for assumptions.
467
+
468
+ Examples
469
+ ========
470
+
471
+ >>> from sympy import assuming, Q, ask
472
+ >>> from sympy.abc import x, y
473
+ >>> print(ask(Q.integer(x + y)))
474
+ None
475
+ >>> with assuming(Q.integer(x), Q.integer(y)):
476
+ ... print(ask(Q.integer(x + y)))
477
+ True
478
+ """
479
+ old_global_assumptions = global_assumptions.copy()
480
+ global_assumptions.update(assumptions)
481
+ try:
482
+ yield
483
+ finally:
484
+ global_assumptions.clear()
485
+ global_assumptions.update(old_global_assumptions)
.venv/lib/python3.13/site-packages/sympy/assumptions/cnf.py ADDED
@@ -0,0 +1,445 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ The classes used here are for the internal use of assumptions system
3
+ only and should not be used anywhere else as these do not possess the
4
+ signatures common to SymPy objects. For general use of logic constructs
5
+ please refer to sympy.logic classes And, Or, Not, etc.
6
+ """
7
+ from itertools import combinations, product, zip_longest
8
+ from sympy.assumptions.assume import AppliedPredicate, Predicate
9
+ from sympy.core.relational import Eq, Ne, Gt, Lt, Ge, Le
10
+ from sympy.core.singleton import S
11
+ from sympy.logic.boolalg import Or, And, Not, Xnor
12
+ from sympy.logic.boolalg import (Equivalent, ITE, Implies, Nand, Nor, Xor)
13
+
14
+
15
+ class Literal:
16
+ """
17
+ The smallest element of a CNF object.
18
+
19
+ Parameters
20
+ ==========
21
+
22
+ lit : Boolean expression
23
+
24
+ is_Not : bool
25
+
26
+ Examples
27
+ ========
28
+
29
+ >>> from sympy import Q
30
+ >>> from sympy.assumptions.cnf import Literal
31
+ >>> from sympy.abc import x
32
+ >>> Literal(Q.even(x))
33
+ Literal(Q.even(x), False)
34
+ >>> Literal(~Q.even(x))
35
+ Literal(Q.even(x), True)
36
+ """
37
+
38
+ def __new__(cls, lit, is_Not=False):
39
+ if isinstance(lit, Not):
40
+ lit = lit.args[0]
41
+ is_Not = True
42
+ elif isinstance(lit, (AND, OR, Literal)):
43
+ return ~lit if is_Not else lit
44
+ obj = super().__new__(cls)
45
+ obj.lit = lit
46
+ obj.is_Not = is_Not
47
+ return obj
48
+
49
+ @property
50
+ def arg(self):
51
+ return self.lit
52
+
53
+ def rcall(self, expr):
54
+ if callable(self.lit):
55
+ lit = self.lit(expr)
56
+ else:
57
+ lit = self.lit.apply(expr)
58
+ return type(self)(lit, self.is_Not)
59
+
60
+ def __invert__(self):
61
+ is_Not = not self.is_Not
62
+ return Literal(self.lit, is_Not)
63
+
64
+ def __str__(self):
65
+ return '{}({}, {})'.format(type(self).__name__, self.lit, self.is_Not)
66
+
67
+ __repr__ = __str__
68
+
69
+ def __eq__(self, other):
70
+ return self.arg == other.arg and self.is_Not == other.is_Not
71
+
72
+ def __hash__(self):
73
+ h = hash((type(self).__name__, self.arg, self.is_Not))
74
+ return h
75
+
76
+
77
+ class OR:
78
+ """
79
+ A low-level implementation for Or
80
+ """
81
+ def __init__(self, *args):
82
+ self._args = args
83
+
84
+ @property
85
+ def args(self):
86
+ return sorted(self._args, key=str)
87
+
88
+ def rcall(self, expr):
89
+ return type(self)(*[arg.rcall(expr)
90
+ for arg in self._args
91
+ ])
92
+
93
+ def __invert__(self):
94
+ return AND(*[~arg for arg in self._args])
95
+
96
+ def __hash__(self):
97
+ return hash((type(self).__name__,) + tuple(self.args))
98
+
99
+ def __eq__(self, other):
100
+ return self.args == other.args
101
+
102
+ def __str__(self):
103
+ s = '(' + ' | '.join([str(arg) for arg in self.args]) + ')'
104
+ return s
105
+
106
+ __repr__ = __str__
107
+
108
+
109
+ class AND:
110
+ """
111
+ A low-level implementation for And
112
+ """
113
+ def __init__(self, *args):
114
+ self._args = args
115
+
116
+ def __invert__(self):
117
+ return OR(*[~arg for arg in self._args])
118
+
119
+ @property
120
+ def args(self):
121
+ return sorted(self._args, key=str)
122
+
123
+ def rcall(self, expr):
124
+ return type(self)(*[arg.rcall(expr)
125
+ for arg in self._args
126
+ ])
127
+
128
+ def __hash__(self):
129
+ return hash((type(self).__name__,) + tuple(self.args))
130
+
131
+ def __eq__(self, other):
132
+ return self.args == other.args
133
+
134
+ def __str__(self):
135
+ s = '('+' & '.join([str(arg) for arg in self.args])+')'
136
+ return s
137
+
138
+ __repr__ = __str__
139
+
140
+
141
+ def to_NNF(expr, composite_map=None):
142
+ """
143
+ Generates the Negation Normal Form of any boolean expression in terms
144
+ of AND, OR, and Literal objects.
145
+
146
+ Examples
147
+ ========
148
+
149
+ >>> from sympy import Q, Eq
150
+ >>> from sympy.assumptions.cnf import to_NNF
151
+ >>> from sympy.abc import x, y
152
+ >>> expr = Q.even(x) & ~Q.positive(x)
153
+ >>> to_NNF(expr)
154
+ (Literal(Q.even(x), False) & Literal(Q.positive(x), True))
155
+
156
+ Supported boolean objects are converted to corresponding predicates.
157
+
158
+ >>> to_NNF(Eq(x, y))
159
+ Literal(Q.eq(x, y), False)
160
+
161
+ If ``composite_map`` argument is given, ``to_NNF`` decomposes the
162
+ specified predicate into a combination of primitive predicates.
163
+
164
+ >>> cmap = {Q.nonpositive: Q.negative | Q.zero}
165
+ >>> to_NNF(Q.nonpositive, cmap)
166
+ (Literal(Q.negative, False) | Literal(Q.zero, False))
167
+ >>> to_NNF(Q.nonpositive(x), cmap)
168
+ (Literal(Q.negative(x), False) | Literal(Q.zero(x), False))
169
+ """
170
+ from sympy.assumptions.ask import Q
171
+
172
+ if composite_map is None:
173
+ composite_map = {}
174
+
175
+
176
+ binrelpreds = {Eq: Q.eq, Ne: Q.ne, Gt: Q.gt, Lt: Q.lt, Ge: Q.ge, Le: Q.le}
177
+ if type(expr) in binrelpreds:
178
+ pred = binrelpreds[type(expr)]
179
+ expr = pred(*expr.args)
180
+
181
+ if isinstance(expr, Not):
182
+ arg = expr.args[0]
183
+ tmp = to_NNF(arg, composite_map) # Strategy: negate the NNF of expr
184
+ return ~tmp
185
+
186
+ if isinstance(expr, Or):
187
+ return OR(*[to_NNF(x, composite_map) for x in Or.make_args(expr)])
188
+
189
+ if isinstance(expr, And):
190
+ return AND(*[to_NNF(x, composite_map) for x in And.make_args(expr)])
191
+
192
+ if isinstance(expr, Nand):
193
+ tmp = AND(*[to_NNF(x, composite_map) for x in expr.args])
194
+ return ~tmp
195
+
196
+ if isinstance(expr, Nor):
197
+ tmp = OR(*[to_NNF(x, composite_map) for x in expr.args])
198
+ return ~tmp
199
+
200
+ if isinstance(expr, Xor):
201
+ cnfs = []
202
+ for i in range(0, len(expr.args) + 1, 2):
203
+ for neg in combinations(expr.args, i):
204
+ clause = [~to_NNF(s, composite_map) if s in neg else to_NNF(s, composite_map)
205
+ for s in expr.args]
206
+ cnfs.append(OR(*clause))
207
+ return AND(*cnfs)
208
+
209
+ if isinstance(expr, Xnor):
210
+ cnfs = []
211
+ for i in range(0, len(expr.args) + 1, 2):
212
+ for neg in combinations(expr.args, i):
213
+ clause = [~to_NNF(s, composite_map) if s in neg else to_NNF(s, composite_map)
214
+ for s in expr.args]
215
+ cnfs.append(OR(*clause))
216
+ return ~AND(*cnfs)
217
+
218
+ if isinstance(expr, Implies):
219
+ L, R = to_NNF(expr.args[0], composite_map), to_NNF(expr.args[1], composite_map)
220
+ return OR(~L, R)
221
+
222
+ if isinstance(expr, Equivalent):
223
+ cnfs = []
224
+ for a, b in zip_longest(expr.args, expr.args[1:], fillvalue=expr.args[0]):
225
+ a = to_NNF(a, composite_map)
226
+ b = to_NNF(b, composite_map)
227
+ cnfs.append(OR(~a, b))
228
+ return AND(*cnfs)
229
+
230
+ if isinstance(expr, ITE):
231
+ L = to_NNF(expr.args[0], composite_map)
232
+ M = to_NNF(expr.args[1], composite_map)
233
+ R = to_NNF(expr.args[2], composite_map)
234
+ return AND(OR(~L, M), OR(L, R))
235
+
236
+ if isinstance(expr, AppliedPredicate):
237
+ pred, args = expr.function, expr.arguments
238
+ newpred = composite_map.get(pred, None)
239
+ if newpred is not None:
240
+ return to_NNF(newpred.rcall(*args), composite_map)
241
+
242
+ if isinstance(expr, Predicate):
243
+ newpred = composite_map.get(expr, None)
244
+ if newpred is not None:
245
+ return to_NNF(newpred, composite_map)
246
+
247
+ return Literal(expr)
248
+
249
+
250
+ def distribute_AND_over_OR(expr):
251
+ """
252
+ Distributes AND over OR in the NNF expression.
253
+ Returns the result( Conjunctive Normal Form of expression)
254
+ as a CNF object.
255
+ """
256
+ if not isinstance(expr, (AND, OR)):
257
+ tmp = set()
258
+ tmp.add(frozenset((expr,)))
259
+ return CNF(tmp)
260
+
261
+ if isinstance(expr, OR):
262
+ return CNF.all_or(*[distribute_AND_over_OR(arg)
263
+ for arg in expr._args])
264
+
265
+ if isinstance(expr, AND):
266
+ return CNF.all_and(*[distribute_AND_over_OR(arg)
267
+ for arg in expr._args])
268
+
269
+
270
+ class CNF:
271
+ """
272
+ Class to represent CNF of a Boolean expression.
273
+ Consists of set of clauses, which themselves are stored as
274
+ frozenset of Literal objects.
275
+
276
+ Examples
277
+ ========
278
+
279
+ >>> from sympy import Q
280
+ >>> from sympy.assumptions.cnf import CNF
281
+ >>> from sympy.abc import x
282
+ >>> cnf = CNF.from_prop(Q.real(x) & ~Q.zero(x))
283
+ >>> cnf.clauses
284
+ {frozenset({Literal(Q.zero(x), True)}),
285
+ frozenset({Literal(Q.negative(x), False),
286
+ Literal(Q.positive(x), False), Literal(Q.zero(x), False)})}
287
+ """
288
+ def __init__(self, clauses=None):
289
+ if not clauses:
290
+ clauses = set()
291
+ self.clauses = clauses
292
+
293
+ def add(self, prop):
294
+ clauses = CNF.to_CNF(prop).clauses
295
+ self.add_clauses(clauses)
296
+
297
+ def __str__(self):
298
+ s = ' & '.join(
299
+ ['(' + ' | '.join([str(lit) for lit in clause]) +')'
300
+ for clause in self.clauses]
301
+ )
302
+ return s
303
+
304
+ def extend(self, props):
305
+ for p in props:
306
+ self.add(p)
307
+ return self
308
+
309
+ def copy(self):
310
+ return CNF(set(self.clauses))
311
+
312
+ def add_clauses(self, clauses):
313
+ self.clauses |= clauses
314
+
315
+ @classmethod
316
+ def from_prop(cls, prop):
317
+ res = cls()
318
+ res.add(prop)
319
+ return res
320
+
321
+ def __iand__(self, other):
322
+ self.add_clauses(other.clauses)
323
+ return self
324
+
325
+ def all_predicates(self):
326
+ predicates = set()
327
+ for c in self.clauses:
328
+ predicates |= {arg.lit for arg in c}
329
+ return predicates
330
+
331
+ def _or(self, cnf):
332
+ clauses = set()
333
+ for a, b in product(self.clauses, cnf.clauses):
334
+ tmp = set(a)
335
+ tmp.update(b)
336
+ clauses.add(frozenset(tmp))
337
+ return CNF(clauses)
338
+
339
+ def _and(self, cnf):
340
+ clauses = self.clauses.union(cnf.clauses)
341
+ return CNF(clauses)
342
+
343
+ def _not(self):
344
+ clss = list(self.clauses)
345
+ ll = {frozenset((~x,)) for x in clss[-1]}
346
+ ll = CNF(ll)
347
+
348
+ for rest in clss[:-1]:
349
+ p = {frozenset((~x,)) for x in rest}
350
+ ll = ll._or(CNF(p))
351
+ return ll
352
+
353
+ def rcall(self, expr):
354
+ clause_list = []
355
+ for clause in self.clauses:
356
+ lits = [arg.rcall(expr) for arg in clause]
357
+ clause_list.append(OR(*lits))
358
+ expr = AND(*clause_list)
359
+ return distribute_AND_over_OR(expr)
360
+
361
+ @classmethod
362
+ def all_or(cls, *cnfs):
363
+ b = cnfs[0].copy()
364
+ for rest in cnfs[1:]:
365
+ b = b._or(rest)
366
+ return b
367
+
368
+ @classmethod
369
+ def all_and(cls, *cnfs):
370
+ b = cnfs[0].copy()
371
+ for rest in cnfs[1:]:
372
+ b = b._and(rest)
373
+ return b
374
+
375
+ @classmethod
376
+ def to_CNF(cls, expr):
377
+ from sympy.assumptions.facts import get_composite_predicates
378
+ expr = to_NNF(expr, get_composite_predicates())
379
+ expr = distribute_AND_over_OR(expr)
380
+ return expr
381
+
382
+ @classmethod
383
+ def CNF_to_cnf(cls, cnf):
384
+ """
385
+ Converts CNF object to SymPy's boolean expression
386
+ retaining the form of expression.
387
+ """
388
+ def remove_literal(arg):
389
+ return Not(arg.lit) if arg.is_Not else arg.lit
390
+
391
+ return And(*(Or(*(remove_literal(arg) for arg in clause)) for clause in cnf.clauses))
392
+
393
+
394
+ class EncodedCNF:
395
+ """
396
+ Class for encoding the CNF expression.
397
+ """
398
+ def __init__(self, data=None, encoding=None):
399
+ if not data and not encoding:
400
+ data = []
401
+ encoding = {}
402
+ self.data = data
403
+ self.encoding = encoding
404
+ self._symbols = list(encoding.keys())
405
+
406
+ def from_cnf(self, cnf):
407
+ self._symbols = list(cnf.all_predicates())
408
+ n = len(self._symbols)
409
+ self.encoding = dict(zip(self._symbols, range(1, n + 1)))
410
+ self.data = [self.encode(clause) for clause in cnf.clauses]
411
+
412
+ @property
413
+ def symbols(self):
414
+ return self._symbols
415
+
416
+ @property
417
+ def variables(self):
418
+ return range(1, len(self._symbols) + 1)
419
+
420
+ def copy(self):
421
+ new_data = [set(clause) for clause in self.data]
422
+ return EncodedCNF(new_data, dict(self.encoding))
423
+
424
+ def add_prop(self, prop):
425
+ cnf = CNF.from_prop(prop)
426
+ self.add_from_cnf(cnf)
427
+
428
+ def add_from_cnf(self, cnf):
429
+ clauses = [self.encode(clause) for clause in cnf.clauses]
430
+ self.data += clauses
431
+
432
+ def encode_arg(self, arg):
433
+ literal = arg.lit
434
+ value = self.encoding.get(literal, None)
435
+ if value is None:
436
+ n = len(self._symbols)
437
+ self._symbols.append(literal)
438
+ value = self.encoding[literal] = n + 1
439
+ if arg.is_Not:
440
+ return -value
441
+ else:
442
+ return value
443
+
444
+ def encode(self, clause):
445
+ return {self.encode_arg(arg) if not arg.lit == S.false else 0 for arg in clause}
.venv/lib/python3.13/site-packages/sympy/assumptions/facts.py ADDED
@@ -0,0 +1,270 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Known facts in assumptions module.
3
+
4
+ This module defines the facts between unary predicates in ``get_known_facts()``,
5
+ and supports functions to generate the contents in
6
+ ``sympy.assumptions.ask_generated`` file.
7
+ """
8
+
9
+ from sympy.assumptions.ask import Q
10
+ from sympy.assumptions.assume import AppliedPredicate
11
+ from sympy.core.cache import cacheit
12
+ from sympy.core.symbol import Symbol
13
+ from sympy.logic.boolalg import (to_cnf, And, Not, Implies, Equivalent,
14
+ Exclusive,)
15
+ from sympy.logic.inference import satisfiable
16
+
17
+
18
+ @cacheit
19
+ def get_composite_predicates():
20
+ # To reduce the complexity of sat solver, these predicates are
21
+ # transformed into the combination of primitive predicates.
22
+ return {
23
+ Q.real : Q.negative | Q.zero | Q.positive,
24
+ Q.integer : Q.even | Q.odd,
25
+ Q.nonpositive : Q.negative | Q.zero,
26
+ Q.nonzero : Q.negative | Q.positive,
27
+ Q.nonnegative : Q.zero | Q.positive,
28
+ Q.extended_real : Q.negative_infinite | Q.negative | Q.zero | Q.positive | Q.positive_infinite,
29
+ Q.extended_positive: Q.positive | Q.positive_infinite,
30
+ Q.extended_negative: Q.negative | Q.negative_infinite,
31
+ Q.extended_nonzero: Q.negative_infinite | Q.negative | Q.positive | Q.positive_infinite,
32
+ Q.extended_nonpositive: Q.negative_infinite | Q.negative | Q.zero,
33
+ Q.extended_nonnegative: Q.zero | Q.positive | Q.positive_infinite,
34
+ Q.complex : Q.algebraic | Q.transcendental
35
+ }
36
+
37
+
38
+ @cacheit
39
+ def get_known_facts(x=None):
40
+ """
41
+ Facts between unary predicates.
42
+
43
+ Parameters
44
+ ==========
45
+
46
+ x : Symbol, optional
47
+ Placeholder symbol for unary facts. Default is ``Symbol('x')``.
48
+
49
+ Returns
50
+ =======
51
+
52
+ fact : Known facts in conjugated normal form.
53
+
54
+ """
55
+ if x is None:
56
+ x = Symbol('x')
57
+
58
+ fact = And(
59
+ get_number_facts(x),
60
+ get_matrix_facts(x)
61
+ )
62
+ return fact
63
+
64
+
65
+ @cacheit
66
+ def get_number_facts(x = None):
67
+ """
68
+ Facts between unary number predicates.
69
+
70
+ Parameters
71
+ ==========
72
+
73
+ x : Symbol, optional
74
+ Placeholder symbol for unary facts. Default is ``Symbol('x')``.
75
+
76
+ Returns
77
+ =======
78
+
79
+ fact : Known facts in conjugated normal form.
80
+
81
+ """
82
+ if x is None:
83
+ x = Symbol('x')
84
+
85
+ fact = And(
86
+ # primitive predicates for extended real exclude each other.
87
+ Exclusive(Q.negative_infinite(x), Q.negative(x), Q.zero(x),
88
+ Q.positive(x), Q.positive_infinite(x)),
89
+
90
+ # build complex plane
91
+ Exclusive(Q.real(x), Q.imaginary(x)),
92
+ Implies(Q.real(x) | Q.imaginary(x), Q.complex(x)),
93
+
94
+ # other subsets of complex
95
+ Exclusive(Q.transcendental(x), Q.algebraic(x)),
96
+ Equivalent(Q.real(x), Q.rational(x) | Q.irrational(x)),
97
+ Exclusive(Q.irrational(x), Q.rational(x)),
98
+ Implies(Q.rational(x), Q.algebraic(x)),
99
+
100
+ # integers
101
+ Exclusive(Q.even(x), Q.odd(x)),
102
+ Implies(Q.integer(x), Q.rational(x)),
103
+ Implies(Q.zero(x), Q.even(x)),
104
+ Exclusive(Q.composite(x), Q.prime(x)),
105
+ Implies(Q.composite(x) | Q.prime(x), Q.integer(x) & Q.positive(x)),
106
+ Implies(Q.even(x) & Q.positive(x) & ~Q.prime(x), Q.composite(x)),
107
+
108
+ # hermitian and antihermitian
109
+ Implies(Q.real(x), Q.hermitian(x)),
110
+ Implies(Q.imaginary(x), Q.antihermitian(x)),
111
+ Implies(Q.zero(x), Q.hermitian(x) | Q.antihermitian(x)),
112
+
113
+ # define finity and infinity, and build extended real line
114
+ Exclusive(Q.infinite(x), Q.finite(x)),
115
+ Implies(Q.complex(x), Q.finite(x)),
116
+ Implies(Q.negative_infinite(x) | Q.positive_infinite(x), Q.infinite(x)),
117
+
118
+ # commutativity
119
+ Implies(Q.finite(x) | Q.infinite(x), Q.commutative(x)),
120
+ )
121
+ return fact
122
+
123
+
124
+ @cacheit
125
+ def get_matrix_facts(x = None):
126
+ """
127
+ Facts between unary matrix predicates.
128
+
129
+ Parameters
130
+ ==========
131
+
132
+ x : Symbol, optional
133
+ Placeholder symbol for unary facts. Default is ``Symbol('x')``.
134
+
135
+ Returns
136
+ =======
137
+
138
+ fact : Known facts in conjugated normal form.
139
+
140
+ """
141
+ if x is None:
142
+ x = Symbol('x')
143
+
144
+ fact = And(
145
+ # matrices
146
+ Implies(Q.orthogonal(x), Q.positive_definite(x)),
147
+ Implies(Q.orthogonal(x), Q.unitary(x)),
148
+ Implies(Q.unitary(x) & Q.real_elements(x), Q.orthogonal(x)),
149
+ Implies(Q.unitary(x), Q.normal(x)),
150
+ Implies(Q.unitary(x), Q.invertible(x)),
151
+ Implies(Q.normal(x), Q.square(x)),
152
+ Implies(Q.diagonal(x), Q.normal(x)),
153
+ Implies(Q.positive_definite(x), Q.invertible(x)),
154
+ Implies(Q.diagonal(x), Q.upper_triangular(x)),
155
+ Implies(Q.diagonal(x), Q.lower_triangular(x)),
156
+ Implies(Q.lower_triangular(x), Q.triangular(x)),
157
+ Implies(Q.upper_triangular(x), Q.triangular(x)),
158
+ Implies(Q.triangular(x), Q.upper_triangular(x) | Q.lower_triangular(x)),
159
+ Implies(Q.upper_triangular(x) & Q.lower_triangular(x), Q.diagonal(x)),
160
+ Implies(Q.diagonal(x), Q.symmetric(x)),
161
+ Implies(Q.unit_triangular(x), Q.triangular(x)),
162
+ Implies(Q.invertible(x), Q.fullrank(x)),
163
+ Implies(Q.invertible(x), Q.square(x)),
164
+ Implies(Q.symmetric(x), Q.square(x)),
165
+ Implies(Q.fullrank(x) & Q.square(x), Q.invertible(x)),
166
+ Equivalent(Q.invertible(x), ~Q.singular(x)),
167
+ Implies(Q.integer_elements(x), Q.real_elements(x)),
168
+ Implies(Q.real_elements(x), Q.complex_elements(x)),
169
+ )
170
+ return fact
171
+
172
+
173
+
174
+ def generate_known_facts_dict(keys, fact):
175
+ """
176
+ Computes and returns a dictionary which contains the relations between
177
+ unary predicates.
178
+
179
+ Each key is a predicate, and item is two groups of predicates.
180
+ First group contains the predicates which are implied by the key, and
181
+ second group contains the predicates which are rejected by the key.
182
+
183
+ All predicates in *keys* and *fact* must be unary and have same placeholder
184
+ symbol.
185
+
186
+ Parameters
187
+ ==========
188
+
189
+ keys : list of AppliedPredicate instances.
190
+
191
+ fact : Fact between predicates in conjugated normal form.
192
+
193
+ Examples
194
+ ========
195
+
196
+ >>> from sympy import Q, And, Implies
197
+ >>> from sympy.assumptions.facts import generate_known_facts_dict
198
+ >>> from sympy.abc import x
199
+ >>> keys = [Q.even(x), Q.odd(x), Q.zero(x)]
200
+ >>> fact = And(Implies(Q.even(x), ~Q.odd(x)),
201
+ ... Implies(Q.zero(x), Q.even(x)))
202
+ >>> generate_known_facts_dict(keys, fact)
203
+ {Q.even: ({Q.even}, {Q.odd}),
204
+ Q.odd: ({Q.odd}, {Q.even, Q.zero}),
205
+ Q.zero: ({Q.even, Q.zero}, {Q.odd})}
206
+ """
207
+ fact_cnf = to_cnf(fact)
208
+ mapping = single_fact_lookup(keys, fact_cnf)
209
+
210
+ ret = {}
211
+ for key, value in mapping.items():
212
+ implied = set()
213
+ rejected = set()
214
+ for expr in value:
215
+ if isinstance(expr, AppliedPredicate):
216
+ implied.add(expr.function)
217
+ elif isinstance(expr, Not):
218
+ pred = expr.args[0]
219
+ rejected.add(pred.function)
220
+ ret[key.function] = (implied, rejected)
221
+ return ret
222
+
223
+
224
+ @cacheit
225
+ def get_known_facts_keys():
226
+ """
227
+ Return every unary predicates registered to ``Q``.
228
+
229
+ This function is used to generate the keys for
230
+ ``generate_known_facts_dict``.
231
+
232
+ """
233
+ # exclude polyadic predicates
234
+ exclude = {Q.eq, Q.ne, Q.gt, Q.lt, Q.ge, Q.le}
235
+
236
+ result = []
237
+ for attr in Q.__class__.__dict__:
238
+ if attr.startswith('__'):
239
+ continue
240
+ pred = getattr(Q, attr)
241
+ if pred in exclude:
242
+ continue
243
+ result.append(pred)
244
+ return result
245
+
246
+
247
+ def single_fact_lookup(known_facts_keys, known_facts_cnf):
248
+ # Return the dictionary for quick lookup of single fact
249
+ mapping = {}
250
+ for key in known_facts_keys:
251
+ mapping[key] = {key}
252
+ for other_key in known_facts_keys:
253
+ if other_key != key:
254
+ if ask_full_inference(other_key, key, known_facts_cnf):
255
+ mapping[key].add(other_key)
256
+ if ask_full_inference(~other_key, key, known_facts_cnf):
257
+ mapping[key].add(~other_key)
258
+ return mapping
259
+
260
+
261
+ def ask_full_inference(proposition, assumptions, known_facts_cnf):
262
+ """
263
+ Method for inferring properties about objects.
264
+
265
+ """
266
+ if not satisfiable(And(known_facts_cnf, assumptions, proposition)):
267
+ return False
268
+ if not satisfiable(And(known_facts_cnf, assumptions, Not(proposition))):
269
+ return True
270
+ return None
.venv/lib/python3.13/site-packages/sympy/assumptions/lra_satask.py ADDED
@@ -0,0 +1,286 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sympy.assumptions.assume import global_assumptions
2
+ from sympy.assumptions.cnf import CNF, EncodedCNF
3
+ from sympy.assumptions.ask import Q
4
+ from sympy.logic.inference import satisfiable
5
+ from sympy.logic.algorithms.lra_theory import UnhandledInput, ALLOWED_PRED
6
+ from sympy.matrices.kind import MatrixKind
7
+ from sympy.core.kind import NumberKind
8
+ from sympy.assumptions.assume import AppliedPredicate
9
+ from sympy.core.mul import Mul
10
+ from sympy.core.singleton import S
11
+
12
+
13
+ def lra_satask(proposition, assumptions=True, context=global_assumptions):
14
+ """
15
+ Function to evaluate the proposition with assumptions using SAT algorithm
16
+ in conjunction with an Linear Real Arithmetic theory solver.
17
+
18
+ Used to handle inequalities. Should eventually be depreciated and combined
19
+ into satask, but infinity handling and other things need to be implemented
20
+ before that can happen.
21
+ """
22
+ props = CNF.from_prop(proposition)
23
+ _props = CNF.from_prop(~proposition)
24
+
25
+ cnf = CNF.from_prop(assumptions)
26
+ assumptions = EncodedCNF()
27
+ assumptions.from_cnf(cnf)
28
+
29
+ context_cnf = CNF()
30
+ if context:
31
+ context_cnf = context_cnf.extend(context)
32
+
33
+ assumptions.add_from_cnf(context_cnf)
34
+
35
+ return check_satisfiability(props, _props, assumptions)
36
+
37
+ # Some predicates such as Q.prime can't be handled by lra_satask.
38
+ # For example, (x > 0) & (x < 1) & Q.prime(x) is unsat but lra_satask would think it was sat.
39
+ # WHITE_LIST is a list of predicates that can always be handled.
40
+ WHITE_LIST = ALLOWED_PRED | {Q.positive, Q.negative, Q.zero, Q.nonzero, Q.nonpositive, Q.nonnegative,
41
+ Q.extended_positive, Q.extended_negative, Q.extended_nonpositive,
42
+ Q.extended_negative, Q.extended_nonzero, Q.negative_infinite,
43
+ Q.positive_infinite}
44
+
45
+
46
+ def check_satisfiability(prop, _prop, factbase):
47
+ sat_true = factbase.copy()
48
+ sat_false = factbase.copy()
49
+ sat_true.add_from_cnf(prop)
50
+ sat_false.add_from_cnf(_prop)
51
+
52
+ all_pred, all_exprs = get_all_pred_and_expr_from_enc_cnf(sat_true)
53
+
54
+ for pred in all_pred:
55
+ if pred.function not in WHITE_LIST and pred.function != Q.ne:
56
+ raise UnhandledInput(f"LRASolver: {pred} is an unhandled predicate")
57
+ for expr in all_exprs:
58
+ if expr.kind == MatrixKind(NumberKind):
59
+ raise UnhandledInput(f"LRASolver: {expr} is of MatrixKind")
60
+ if expr == S.NaN:
61
+ raise UnhandledInput("LRASolver: nan")
62
+
63
+ # convert old assumptions into predicates and add them to sat_true and sat_false
64
+ # also check for unhandled predicates
65
+ for assm in extract_pred_from_old_assum(all_exprs):
66
+ n = len(sat_true.encoding)
67
+ if assm not in sat_true.encoding:
68
+ sat_true.encoding[assm] = n+1
69
+ sat_true.data.append([sat_true.encoding[assm]])
70
+
71
+ n = len(sat_false.encoding)
72
+ if assm not in sat_false.encoding:
73
+ sat_false.encoding[assm] = n+1
74
+ sat_false.data.append([sat_false.encoding[assm]])
75
+
76
+
77
+ sat_true = _preprocess(sat_true)
78
+ sat_false = _preprocess(sat_false)
79
+
80
+ can_be_true = satisfiable(sat_true, use_lra_theory=True) is not False
81
+ can_be_false = satisfiable(sat_false, use_lra_theory=True) is not False
82
+
83
+ if can_be_true and can_be_false:
84
+ return None
85
+
86
+ if can_be_true and not can_be_false:
87
+ return True
88
+
89
+ if not can_be_true and can_be_false:
90
+ return False
91
+
92
+ if not can_be_true and not can_be_false:
93
+ raise ValueError("Inconsistent assumptions")
94
+
95
+
96
+ def _preprocess(enc_cnf):
97
+ """
98
+ Returns an encoded cnf with only Q.eq, Q.gt, Q.lt,
99
+ Q.ge, and Q.le predicate.
100
+
101
+ Converts every unequality into a disjunction of strict
102
+ inequalities. For example, x != 3 would become
103
+ x < 3 OR x > 3.
104
+
105
+ Also converts all negated Q.ne predicates into
106
+ equalities.
107
+ """
108
+
109
+ # loops through each literal in each clause
110
+ # to construct a new, preprocessed encodedCNF
111
+
112
+ enc_cnf = enc_cnf.copy()
113
+ cur_enc = 1
114
+ rev_encoding = {value: key for key, value in enc_cnf.encoding.items()}
115
+
116
+ new_encoding = {}
117
+ new_data = []
118
+ for clause in enc_cnf.data:
119
+ new_clause = []
120
+ for lit in clause:
121
+ if lit == 0:
122
+ new_clause.append(lit)
123
+ new_encoding[lit] = False
124
+ continue
125
+ prop = rev_encoding[abs(lit)]
126
+ negated = lit < 0
127
+ sign = (lit > 0) - (lit < 0)
128
+
129
+ prop = _pred_to_binrel(prop)
130
+
131
+ if not isinstance(prop, AppliedPredicate):
132
+ if prop not in new_encoding:
133
+ new_encoding[prop] = cur_enc
134
+ cur_enc += 1
135
+ lit = new_encoding[prop]
136
+ new_clause.append(sign*lit)
137
+ continue
138
+
139
+
140
+ if negated and prop.function == Q.eq:
141
+ negated = False
142
+ prop = Q.ne(*prop.arguments)
143
+
144
+ if prop.function == Q.ne:
145
+ arg1, arg2 = prop.arguments
146
+ if negated:
147
+ new_prop = Q.eq(arg1, arg2)
148
+ if new_prop not in new_encoding:
149
+ new_encoding[new_prop] = cur_enc
150
+ cur_enc += 1
151
+
152
+ new_enc = new_encoding[new_prop]
153
+ new_clause.append(new_enc)
154
+ continue
155
+ else:
156
+ new_props = (Q.gt(arg1, arg2), Q.lt(arg1, arg2))
157
+ for new_prop in new_props:
158
+ if new_prop not in new_encoding:
159
+ new_encoding[new_prop] = cur_enc
160
+ cur_enc += 1
161
+
162
+ new_enc = new_encoding[new_prop]
163
+ new_clause.append(new_enc)
164
+ continue
165
+
166
+ if prop.function == Q.eq and negated:
167
+ assert False
168
+
169
+ if prop not in new_encoding:
170
+ new_encoding[prop] = cur_enc
171
+ cur_enc += 1
172
+ new_clause.append(new_encoding[prop]*sign)
173
+ new_data.append(new_clause)
174
+
175
+ assert len(new_encoding) >= cur_enc - 1
176
+
177
+ enc_cnf = EncodedCNF(new_data, new_encoding)
178
+ return enc_cnf
179
+
180
+
181
+ def _pred_to_binrel(pred):
182
+ if not isinstance(pred, AppliedPredicate):
183
+ return pred
184
+
185
+ if pred.function in pred_to_pos_neg_zero:
186
+ f = pred_to_pos_neg_zero[pred.function]
187
+ if f is False:
188
+ return False
189
+ pred = f(pred.arguments[0])
190
+
191
+ if pred.function == Q.positive:
192
+ pred = Q.gt(pred.arguments[0], 0)
193
+ elif pred.function == Q.negative:
194
+ pred = Q.lt(pred.arguments[0], 0)
195
+ elif pred.function == Q.zero:
196
+ pred = Q.eq(pred.arguments[0], 0)
197
+ elif pred.function == Q.nonpositive:
198
+ pred = Q.le(pred.arguments[0], 0)
199
+ elif pred.function == Q.nonnegative:
200
+ pred = Q.ge(pred.arguments[0], 0)
201
+ elif pred.function == Q.nonzero:
202
+ pred = Q.ne(pred.arguments[0], 0)
203
+
204
+ return pred
205
+
206
+ pred_to_pos_neg_zero = {
207
+ Q.extended_positive: Q.positive,
208
+ Q.extended_negative: Q.negative,
209
+ Q.extended_nonpositive: Q.nonpositive,
210
+ Q.extended_negative: Q.negative,
211
+ Q.extended_nonzero: Q.nonzero,
212
+ Q.negative_infinite: False,
213
+ Q.positive_infinite: False
214
+ }
215
+
216
+ def get_all_pred_and_expr_from_enc_cnf(enc_cnf):
217
+ all_exprs = set()
218
+ all_pred = set()
219
+ for pred in enc_cnf.encoding.keys():
220
+ if isinstance(pred, AppliedPredicate):
221
+ all_pred.add(pred)
222
+ all_exprs.update(pred.arguments)
223
+
224
+ return all_pred, all_exprs
225
+
226
+ def extract_pred_from_old_assum(all_exprs):
227
+ """
228
+ Returns a list of relevant new assumption predicate
229
+ based on any old assumptions.
230
+
231
+ Raises an UnhandledInput exception if any of the assumptions are
232
+ unhandled.
233
+
234
+ Ignored predicate:
235
+ - commutative
236
+ - complex
237
+ - algebraic
238
+ - transcendental
239
+ - extended_real
240
+ - real
241
+ - all matrix predicate
242
+ - rational
243
+ - irrational
244
+
245
+ Example
246
+ =======
247
+ >>> from sympy.assumptions.lra_satask import extract_pred_from_old_assum
248
+ >>> from sympy import symbols
249
+ >>> x, y = symbols("x y", positive=True)
250
+ >>> extract_pred_from_old_assum([x, y, 2])
251
+ [Q.positive(x), Q.positive(y)]
252
+ """
253
+ ret = []
254
+ for expr in all_exprs:
255
+ if not hasattr(expr, "free_symbols"):
256
+ continue
257
+ if len(expr.free_symbols) == 0:
258
+ continue
259
+
260
+ if expr.is_real is not True:
261
+ raise UnhandledInput(f"LRASolver: {expr} must be real")
262
+ # test for I times imaginary variable; such expressions are considered real
263
+ if isinstance(expr, Mul) and any(arg.is_real is not True for arg in expr.args):
264
+ raise UnhandledInput(f"LRASolver: {expr} must be real")
265
+
266
+ if expr.is_integer == True and expr.is_zero != True:
267
+ raise UnhandledInput(f"LRASolver: {expr} is an integer")
268
+ if expr.is_integer == False:
269
+ raise UnhandledInput(f"LRASolver: {expr} can't be an integer")
270
+ if expr.is_rational == False:
271
+ raise UnhandledInput(f"LRASolver: {expr} is irational")
272
+
273
+ if expr.is_zero:
274
+ ret.append(Q.zero(expr))
275
+ elif expr.is_positive:
276
+ ret.append(Q.positive(expr))
277
+ elif expr.is_negative:
278
+ ret.append(Q.negative(expr))
279
+ elif expr.is_nonzero:
280
+ ret.append(Q.nonzero(expr))
281
+ elif expr.is_nonpositive:
282
+ ret.append(Q.nonpositive(expr))
283
+ elif expr.is_nonnegative:
284
+ ret.append(Q.nonnegative(expr))
285
+
286
+ return ret
.venv/lib/python3.13/site-packages/sympy/assumptions/refine.py ADDED
@@ -0,0 +1,405 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+ from typing import Callable
3
+
4
+ from sympy.core import S, Add, Expr, Basic, Mul, Pow, Rational
5
+ from sympy.core.logic import fuzzy_not
6
+ from sympy.logic.boolalg import Boolean
7
+
8
+ from sympy.assumptions import ask, Q # type: ignore
9
+
10
+
11
+ def refine(expr, assumptions=True):
12
+ """
13
+ Simplify an expression using assumptions.
14
+
15
+ Explanation
16
+ ===========
17
+
18
+ Unlike :func:`~.simplify` which performs structural simplification
19
+ without any assumption, this function transforms the expression into
20
+ the form which is only valid under certain assumptions. Note that
21
+ ``simplify()`` is generally not done in refining process.
22
+
23
+ Refining boolean expression involves reducing it to ``S.true`` or
24
+ ``S.false``. Unlike :func:`~.ask`, the expression will not be reduced
25
+ if the truth value cannot be determined.
26
+
27
+ Examples
28
+ ========
29
+
30
+ >>> from sympy import refine, sqrt, Q
31
+ >>> from sympy.abc import x
32
+ >>> refine(sqrt(x**2), Q.real(x))
33
+ Abs(x)
34
+ >>> refine(sqrt(x**2), Q.positive(x))
35
+ x
36
+
37
+ >>> refine(Q.real(x), Q.positive(x))
38
+ True
39
+ >>> refine(Q.positive(x), Q.real(x))
40
+ Q.positive(x)
41
+
42
+ See Also
43
+ ========
44
+
45
+ sympy.simplify.simplify.simplify : Structural simplification without assumptions.
46
+ sympy.assumptions.ask.ask : Query for boolean expressions using assumptions.
47
+ """
48
+ if not isinstance(expr, Basic):
49
+ return expr
50
+
51
+ if not expr.is_Atom:
52
+ args = [refine(arg, assumptions) for arg in expr.args]
53
+ # TODO: this will probably not work with Integral or Polynomial
54
+ expr = expr.func(*args)
55
+ if hasattr(expr, '_eval_refine'):
56
+ ref_expr = expr._eval_refine(assumptions)
57
+ if ref_expr is not None:
58
+ return ref_expr
59
+ name = expr.__class__.__name__
60
+ handler = handlers_dict.get(name, None)
61
+ if handler is None:
62
+ return expr
63
+ new_expr = handler(expr, assumptions)
64
+ if (new_expr is None) or (expr == new_expr):
65
+ return expr
66
+ if not isinstance(new_expr, Expr):
67
+ return new_expr
68
+ return refine(new_expr, assumptions)
69
+
70
+
71
+ def refine_abs(expr, assumptions):
72
+ """
73
+ Handler for the absolute value.
74
+
75
+ Examples
76
+ ========
77
+
78
+ >>> from sympy import Q, Abs
79
+ >>> from sympy.assumptions.refine import refine_abs
80
+ >>> from sympy.abc import x
81
+ >>> refine_abs(Abs(x), Q.real(x))
82
+ >>> refine_abs(Abs(x), Q.positive(x))
83
+ x
84
+ >>> refine_abs(Abs(x), Q.negative(x))
85
+ -x
86
+
87
+ """
88
+ from sympy.functions.elementary.complexes import Abs
89
+ arg = expr.args[0]
90
+ if ask(Q.real(arg), assumptions) and \
91
+ fuzzy_not(ask(Q.negative(arg), assumptions)):
92
+ # if it's nonnegative
93
+ return arg
94
+ if ask(Q.negative(arg), assumptions):
95
+ return -arg
96
+ # arg is Mul
97
+ if isinstance(arg, Mul):
98
+ r = [refine(abs(a), assumptions) for a in arg.args]
99
+ non_abs = []
100
+ in_abs = []
101
+ for i in r:
102
+ if isinstance(i, Abs):
103
+ in_abs.append(i.args[0])
104
+ else:
105
+ non_abs.append(i)
106
+ return Mul(*non_abs) * Abs(Mul(*in_abs))
107
+
108
+
109
+ def refine_Pow(expr, assumptions):
110
+ """
111
+ Handler for instances of Pow.
112
+
113
+ Examples
114
+ ========
115
+
116
+ >>> from sympy import Q
117
+ >>> from sympy.assumptions.refine import refine_Pow
118
+ >>> from sympy.abc import x,y,z
119
+ >>> refine_Pow((-1)**x, Q.real(x))
120
+ >>> refine_Pow((-1)**x, Q.even(x))
121
+ 1
122
+ >>> refine_Pow((-1)**x, Q.odd(x))
123
+ -1
124
+
125
+ For powers of -1, even parts of the exponent can be simplified:
126
+
127
+ >>> refine_Pow((-1)**(x+y), Q.even(x))
128
+ (-1)**y
129
+ >>> refine_Pow((-1)**(x+y+z), Q.odd(x) & Q.odd(z))
130
+ (-1)**y
131
+ >>> refine_Pow((-1)**(x+y+2), Q.odd(x))
132
+ (-1)**(y + 1)
133
+ >>> refine_Pow((-1)**(x+3), True)
134
+ (-1)**(x + 1)
135
+
136
+ """
137
+ from sympy.functions.elementary.complexes import Abs
138
+ from sympy.functions import sign
139
+ if isinstance(expr.base, Abs):
140
+ if ask(Q.real(expr.base.args[0]), assumptions) and \
141
+ ask(Q.even(expr.exp), assumptions):
142
+ return expr.base.args[0] ** expr.exp
143
+ if ask(Q.real(expr.base), assumptions):
144
+ if expr.base.is_number:
145
+ if ask(Q.even(expr.exp), assumptions):
146
+ return abs(expr.base) ** expr.exp
147
+ if ask(Q.odd(expr.exp), assumptions):
148
+ return sign(expr.base) * abs(expr.base) ** expr.exp
149
+ if isinstance(expr.exp, Rational):
150
+ if isinstance(expr.base, Pow):
151
+ return abs(expr.base.base) ** (expr.base.exp * expr.exp)
152
+
153
+ if expr.base is S.NegativeOne:
154
+ if expr.exp.is_Add:
155
+
156
+ old = expr
157
+
158
+ # For powers of (-1) we can remove
159
+ # - even terms
160
+ # - pairs of odd terms
161
+ # - a single odd term + 1
162
+ # - A numerical constant N can be replaced with mod(N,2)
163
+
164
+ coeff, terms = expr.exp.as_coeff_add()
165
+ terms = set(terms)
166
+ even_terms = set()
167
+ odd_terms = set()
168
+ initial_number_of_terms = len(terms)
169
+
170
+ for t in terms:
171
+ if ask(Q.even(t), assumptions):
172
+ even_terms.add(t)
173
+ elif ask(Q.odd(t), assumptions):
174
+ odd_terms.add(t)
175
+
176
+ terms -= even_terms
177
+ if len(odd_terms) % 2:
178
+ terms -= odd_terms
179
+ new_coeff = (coeff + S.One) % 2
180
+ else:
181
+ terms -= odd_terms
182
+ new_coeff = coeff % 2
183
+
184
+ if new_coeff != coeff or len(terms) < initial_number_of_terms:
185
+ terms.add(new_coeff)
186
+ expr = expr.base**(Add(*terms))
187
+
188
+ # Handle (-1)**((-1)**n/2 + m/2)
189
+ e2 = 2*expr.exp
190
+ if ask(Q.even(e2), assumptions):
191
+ if e2.could_extract_minus_sign():
192
+ e2 *= expr.base
193
+ if e2.is_Add:
194
+ i, p = e2.as_two_terms()
195
+ if p.is_Pow and p.base is S.NegativeOne:
196
+ if ask(Q.integer(p.exp), assumptions):
197
+ i = (i + 1)/2
198
+ if ask(Q.even(i), assumptions):
199
+ return expr.base**p.exp
200
+ elif ask(Q.odd(i), assumptions):
201
+ return expr.base**(p.exp + 1)
202
+ else:
203
+ return expr.base**(p.exp + i)
204
+
205
+ if old != expr:
206
+ return expr
207
+
208
+
209
+ def refine_atan2(expr, assumptions):
210
+ """
211
+ Handler for the atan2 function.
212
+
213
+ Examples
214
+ ========
215
+
216
+ >>> from sympy import Q, atan2
217
+ >>> from sympy.assumptions.refine import refine_atan2
218
+ >>> from sympy.abc import x, y
219
+ >>> refine_atan2(atan2(y,x), Q.real(y) & Q.positive(x))
220
+ atan(y/x)
221
+ >>> refine_atan2(atan2(y,x), Q.negative(y) & Q.negative(x))
222
+ atan(y/x) - pi
223
+ >>> refine_atan2(atan2(y,x), Q.positive(y) & Q.negative(x))
224
+ atan(y/x) + pi
225
+ >>> refine_atan2(atan2(y,x), Q.zero(y) & Q.negative(x))
226
+ pi
227
+ >>> refine_atan2(atan2(y,x), Q.positive(y) & Q.zero(x))
228
+ pi/2
229
+ >>> refine_atan2(atan2(y,x), Q.negative(y) & Q.zero(x))
230
+ -pi/2
231
+ >>> refine_atan2(atan2(y,x), Q.zero(y) & Q.zero(x))
232
+ nan
233
+ """
234
+ from sympy.functions.elementary.trigonometric import atan
235
+ y, x = expr.args
236
+ if ask(Q.real(y) & Q.positive(x), assumptions):
237
+ return atan(y / x)
238
+ elif ask(Q.negative(y) & Q.negative(x), assumptions):
239
+ return atan(y / x) - S.Pi
240
+ elif ask(Q.positive(y) & Q.negative(x), assumptions):
241
+ return atan(y / x) + S.Pi
242
+ elif ask(Q.zero(y) & Q.negative(x), assumptions):
243
+ return S.Pi
244
+ elif ask(Q.positive(y) & Q.zero(x), assumptions):
245
+ return S.Pi/2
246
+ elif ask(Q.negative(y) & Q.zero(x), assumptions):
247
+ return -S.Pi/2
248
+ elif ask(Q.zero(y) & Q.zero(x), assumptions):
249
+ return S.NaN
250
+ else:
251
+ return expr
252
+
253
+
254
+ def refine_re(expr, assumptions):
255
+ """
256
+ Handler for real part.
257
+
258
+ Examples
259
+ ========
260
+
261
+ >>> from sympy.assumptions.refine import refine_re
262
+ >>> from sympy import Q, re
263
+ >>> from sympy.abc import x
264
+ >>> refine_re(re(x), Q.real(x))
265
+ x
266
+ >>> refine_re(re(x), Q.imaginary(x))
267
+ 0
268
+ """
269
+ arg = expr.args[0]
270
+ if ask(Q.real(arg), assumptions):
271
+ return arg
272
+ if ask(Q.imaginary(arg), assumptions):
273
+ return S.Zero
274
+ return _refine_reim(expr, assumptions)
275
+
276
+
277
+ def refine_im(expr, assumptions):
278
+ """
279
+ Handler for imaginary part.
280
+
281
+ Explanation
282
+ ===========
283
+
284
+ >>> from sympy.assumptions.refine import refine_im
285
+ >>> from sympy import Q, im
286
+ >>> from sympy.abc import x
287
+ >>> refine_im(im(x), Q.real(x))
288
+ 0
289
+ >>> refine_im(im(x), Q.imaginary(x))
290
+ -I*x
291
+ """
292
+ arg = expr.args[0]
293
+ if ask(Q.real(arg), assumptions):
294
+ return S.Zero
295
+ if ask(Q.imaginary(arg), assumptions):
296
+ return - S.ImaginaryUnit * arg
297
+ return _refine_reim(expr, assumptions)
298
+
299
+ def refine_arg(expr, assumptions):
300
+ """
301
+ Handler for complex argument
302
+
303
+ Explanation
304
+ ===========
305
+
306
+ >>> from sympy.assumptions.refine import refine_arg
307
+ >>> from sympy import Q, arg
308
+ >>> from sympy.abc import x
309
+ >>> refine_arg(arg(x), Q.positive(x))
310
+ 0
311
+ >>> refine_arg(arg(x), Q.negative(x))
312
+ pi
313
+ """
314
+ rg = expr.args[0]
315
+ if ask(Q.positive(rg), assumptions):
316
+ return S.Zero
317
+ if ask(Q.negative(rg), assumptions):
318
+ return S.Pi
319
+ return None
320
+
321
+
322
+ def _refine_reim(expr, assumptions):
323
+ # Helper function for refine_re & refine_im
324
+ expanded = expr.expand(complex = True)
325
+ if expanded != expr:
326
+ refined = refine(expanded, assumptions)
327
+ if refined != expanded:
328
+ return refined
329
+ # Best to leave the expression as is
330
+ return None
331
+
332
+
333
+ def refine_sign(expr, assumptions):
334
+ """
335
+ Handler for sign.
336
+
337
+ Examples
338
+ ========
339
+
340
+ >>> from sympy.assumptions.refine import refine_sign
341
+ >>> from sympy import Symbol, Q, sign, im
342
+ >>> x = Symbol('x', real = True)
343
+ >>> expr = sign(x)
344
+ >>> refine_sign(expr, Q.positive(x) & Q.nonzero(x))
345
+ 1
346
+ >>> refine_sign(expr, Q.negative(x) & Q.nonzero(x))
347
+ -1
348
+ >>> refine_sign(expr, Q.zero(x))
349
+ 0
350
+ >>> y = Symbol('y', imaginary = True)
351
+ >>> expr = sign(y)
352
+ >>> refine_sign(expr, Q.positive(im(y)))
353
+ I
354
+ >>> refine_sign(expr, Q.negative(im(y)))
355
+ -I
356
+ """
357
+ arg = expr.args[0]
358
+ if ask(Q.zero(arg), assumptions):
359
+ return S.Zero
360
+ if ask(Q.real(arg)):
361
+ if ask(Q.positive(arg), assumptions):
362
+ return S.One
363
+ if ask(Q.negative(arg), assumptions):
364
+ return S.NegativeOne
365
+ if ask(Q.imaginary(arg)):
366
+ arg_re, arg_im = arg.as_real_imag()
367
+ if ask(Q.positive(arg_im), assumptions):
368
+ return S.ImaginaryUnit
369
+ if ask(Q.negative(arg_im), assumptions):
370
+ return -S.ImaginaryUnit
371
+ return expr
372
+
373
+
374
+ def refine_matrixelement(expr, assumptions):
375
+ """
376
+ Handler for symmetric part.
377
+
378
+ Examples
379
+ ========
380
+
381
+ >>> from sympy.assumptions.refine import refine_matrixelement
382
+ >>> from sympy import MatrixSymbol, Q
383
+ >>> X = MatrixSymbol('X', 3, 3)
384
+ >>> refine_matrixelement(X[0, 1], Q.symmetric(X))
385
+ X[0, 1]
386
+ >>> refine_matrixelement(X[1, 0], Q.symmetric(X))
387
+ X[0, 1]
388
+ """
389
+ from sympy.matrices.expressions.matexpr import MatrixElement
390
+ matrix, i, j = expr.args
391
+ if ask(Q.symmetric(matrix), assumptions):
392
+ if (i - j).could_extract_minus_sign():
393
+ return expr
394
+ return MatrixElement(matrix, j, i)
395
+
396
+ handlers_dict: dict[str, Callable[[Expr, Boolean], Expr]] = {
397
+ 'Abs': refine_abs,
398
+ 'Pow': refine_Pow,
399
+ 'atan2': refine_atan2,
400
+ 're': refine_re,
401
+ 'im': refine_im,
402
+ 'arg': refine_arg,
403
+ 'sign': refine_sign,
404
+ 'MatrixElement': refine_matrixelement
405
+ }
.venv/lib/python3.13/site-packages/sympy/assumptions/satask.py ADDED
@@ -0,0 +1,369 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Module to evaluate the proposition with assumptions using SAT algorithm.
3
+ """
4
+
5
+ from sympy.core.singleton import S
6
+ from sympy.core.symbol import Symbol
7
+ from sympy.core.kind import NumberKind, UndefinedKind
8
+ from sympy.assumptions.ask_generated import get_all_known_matrix_facts, get_all_known_number_facts
9
+ from sympy.assumptions.assume import global_assumptions, AppliedPredicate
10
+ from sympy.assumptions.sathandlers import class_fact_registry
11
+ from sympy.core import oo
12
+ from sympy.logic.inference import satisfiable
13
+ from sympy.assumptions.cnf import CNF, EncodedCNF
14
+ from sympy.matrices.kind import MatrixKind
15
+
16
+
17
+ def satask(proposition, assumptions=True, context=global_assumptions,
18
+ use_known_facts=True, iterations=oo):
19
+ """
20
+ Function to evaluate the proposition with assumptions using SAT algorithm.
21
+
22
+ This function extracts every fact relevant to the expressions composing
23
+ proposition and assumptions. For example, if a predicate containing
24
+ ``Abs(x)`` is proposed, then ``Q.zero(Abs(x)) | Q.positive(Abs(x))``
25
+ will be found and passed to SAT solver because ``Q.nonnegative`` is
26
+ registered as a fact for ``Abs``.
27
+
28
+ Proposition is evaluated to ``True`` or ``False`` if the truth value can be
29
+ determined. If not, ``None`` is returned.
30
+
31
+ Parameters
32
+ ==========
33
+
34
+ proposition : Any boolean expression.
35
+ Proposition which will be evaluated to boolean value.
36
+
37
+ assumptions : Any boolean expression, optional.
38
+ Local assumptions to evaluate the *proposition*.
39
+
40
+ context : AssumptionsContext, optional.
41
+ Default assumptions to evaluate the *proposition*. By default,
42
+ this is ``sympy.assumptions.global_assumptions`` variable.
43
+
44
+ use_known_facts : bool, optional.
45
+ If ``True``, facts from ``sympy.assumptions.ask_generated``
46
+ module are passed to SAT solver as well.
47
+
48
+ iterations : int, optional.
49
+ Number of times that relevant facts are recursively extracted.
50
+ Default is infinite times until no new fact is found.
51
+
52
+ Returns
53
+ =======
54
+
55
+ ``True``, ``False``, or ``None``
56
+
57
+ Examples
58
+ ========
59
+
60
+ >>> from sympy import Abs, Q
61
+ >>> from sympy.assumptions.satask import satask
62
+ >>> from sympy.abc import x
63
+ >>> satask(Q.zero(Abs(x)), Q.zero(x))
64
+ True
65
+
66
+ """
67
+ props = CNF.from_prop(proposition)
68
+ _props = CNF.from_prop(~proposition)
69
+
70
+ assumptions = CNF.from_prop(assumptions)
71
+
72
+ context_cnf = CNF()
73
+ if context:
74
+ context_cnf = context_cnf.extend(context)
75
+
76
+ sat = get_all_relevant_facts(props, assumptions, context_cnf,
77
+ use_known_facts=use_known_facts, iterations=iterations)
78
+ sat.add_from_cnf(assumptions)
79
+ if context:
80
+ sat.add_from_cnf(context_cnf)
81
+
82
+ return check_satisfiability(props, _props, sat)
83
+
84
+
85
+ def check_satisfiability(prop, _prop, factbase):
86
+ sat_true = factbase.copy()
87
+ sat_false = factbase.copy()
88
+ sat_true.add_from_cnf(prop)
89
+ sat_false.add_from_cnf(_prop)
90
+ can_be_true = satisfiable(sat_true)
91
+ can_be_false = satisfiable(sat_false)
92
+
93
+ if can_be_true and can_be_false:
94
+ return None
95
+
96
+ if can_be_true and not can_be_false:
97
+ return True
98
+
99
+ if not can_be_true and can_be_false:
100
+ return False
101
+
102
+ if not can_be_true and not can_be_false:
103
+ # TODO: Run additional checks to see which combination of the
104
+ # assumptions, global_assumptions, and relevant_facts are
105
+ # inconsistent.
106
+ raise ValueError("Inconsistent assumptions")
107
+
108
+
109
+ def extract_predargs(proposition, assumptions=None, context=None):
110
+ """
111
+ Extract every expression in the argument of predicates from *proposition*,
112
+ *assumptions* and *context*.
113
+
114
+ Parameters
115
+ ==========
116
+
117
+ proposition : sympy.assumptions.cnf.CNF
118
+
119
+ assumptions : sympy.assumptions.cnf.CNF, optional.
120
+
121
+ context : sympy.assumptions.cnf.CNF, optional.
122
+ CNF generated from assumptions context.
123
+
124
+ Examples
125
+ ========
126
+
127
+ >>> from sympy import Q, Abs
128
+ >>> from sympy.assumptions.cnf import CNF
129
+ >>> from sympy.assumptions.satask import extract_predargs
130
+ >>> from sympy.abc import x, y
131
+ >>> props = CNF.from_prop(Q.zero(Abs(x*y)))
132
+ >>> assump = CNF.from_prop(Q.zero(x) & Q.zero(y))
133
+ >>> extract_predargs(props, assump)
134
+ {x, y, Abs(x*y)}
135
+
136
+ """
137
+ req_keys = find_symbols(proposition)
138
+ keys = proposition.all_predicates()
139
+ # XXX: We need this since True/False are not Basic
140
+ lkeys = set()
141
+ if assumptions:
142
+ lkeys |= assumptions.all_predicates()
143
+ if context:
144
+ lkeys |= context.all_predicates()
145
+
146
+ lkeys = lkeys - {S.true, S.false}
147
+ tmp_keys = None
148
+ while tmp_keys != set():
149
+ tmp = set()
150
+ for l in lkeys:
151
+ syms = find_symbols(l)
152
+ if (syms & req_keys) != set():
153
+ tmp |= syms
154
+ tmp_keys = tmp - req_keys
155
+ req_keys |= tmp_keys
156
+ keys |= {l for l in lkeys if find_symbols(l) & req_keys != set()}
157
+
158
+ exprs = set()
159
+ for key in keys:
160
+ if isinstance(key, AppliedPredicate):
161
+ exprs |= set(key.arguments)
162
+ else:
163
+ exprs.add(key)
164
+ return exprs
165
+
166
+ def find_symbols(pred):
167
+ """
168
+ Find every :obj:`~.Symbol` in *pred*.
169
+
170
+ Parameters
171
+ ==========
172
+
173
+ pred : sympy.assumptions.cnf.CNF, or any Expr.
174
+
175
+ """
176
+ if isinstance(pred, CNF):
177
+ symbols = set()
178
+ for a in pred.all_predicates():
179
+ symbols |= find_symbols(a)
180
+ return symbols
181
+ return pred.atoms(Symbol)
182
+
183
+
184
+ def get_relevant_clsfacts(exprs, relevant_facts=None):
185
+ """
186
+ Extract relevant facts from the items in *exprs*. Facts are defined in
187
+ ``assumptions.sathandlers`` module.
188
+
189
+ This function is recursively called by ``get_all_relevant_facts()``.
190
+
191
+ Parameters
192
+ ==========
193
+
194
+ exprs : set
195
+ Expressions whose relevant facts are searched.
196
+
197
+ relevant_facts : sympy.assumptions.cnf.CNF, optional.
198
+ Pre-discovered relevant facts.
199
+
200
+ Returns
201
+ =======
202
+
203
+ exprs : set
204
+ Candidates for next relevant fact searching.
205
+
206
+ relevant_facts : sympy.assumptions.cnf.CNF
207
+ Updated relevant facts.
208
+
209
+ Examples
210
+ ========
211
+
212
+ Here, we will see how facts relevant to ``Abs(x*y)`` are recursively
213
+ extracted. On the first run, set containing the expression is passed
214
+ without pre-discovered relevant facts. The result is a set containing
215
+ candidates for next run, and ``CNF()`` instance containing facts
216
+ which are relevant to ``Abs`` and its argument.
217
+
218
+ >>> from sympy import Abs
219
+ >>> from sympy.assumptions.satask import get_relevant_clsfacts
220
+ >>> from sympy.abc import x, y
221
+ >>> exprs = {Abs(x*y)}
222
+ >>> exprs, facts = get_relevant_clsfacts(exprs)
223
+ >>> exprs
224
+ {x*y}
225
+ >>> facts.clauses #doctest: +SKIP
226
+ {frozenset({Literal(Q.odd(Abs(x*y)), False), Literal(Q.odd(x*y), True)}),
227
+ frozenset({Literal(Q.zero(Abs(x*y)), False), Literal(Q.zero(x*y), True)}),
228
+ frozenset({Literal(Q.even(Abs(x*y)), False), Literal(Q.even(x*y), True)}),
229
+ frozenset({Literal(Q.zero(Abs(x*y)), True), Literal(Q.zero(x*y), False)}),
230
+ frozenset({Literal(Q.even(Abs(x*y)), False),
231
+ Literal(Q.odd(Abs(x*y)), False),
232
+ Literal(Q.odd(x*y), True)}),
233
+ frozenset({Literal(Q.even(Abs(x*y)), False),
234
+ Literal(Q.even(x*y), True),
235
+ Literal(Q.odd(Abs(x*y)), False)}),
236
+ frozenset({Literal(Q.positive(Abs(x*y)), False),
237
+ Literal(Q.zero(Abs(x*y)), False)})}
238
+
239
+ We pass the first run's results to the second run, and get the expressions
240
+ for next run and updated facts.
241
+
242
+ >>> exprs, facts = get_relevant_clsfacts(exprs, relevant_facts=facts)
243
+ >>> exprs
244
+ {x, y}
245
+
246
+ On final run, no more candidate is returned thus we know that all
247
+ relevant facts are successfully retrieved.
248
+
249
+ >>> exprs, facts = get_relevant_clsfacts(exprs, relevant_facts=facts)
250
+ >>> exprs
251
+ set()
252
+
253
+ """
254
+ if not relevant_facts:
255
+ relevant_facts = CNF()
256
+
257
+ newexprs = set()
258
+ for expr in exprs:
259
+ for fact in class_fact_registry(expr):
260
+ newfact = CNF.to_CNF(fact)
261
+ relevant_facts = relevant_facts._and(newfact)
262
+ for key in newfact.all_predicates():
263
+ if isinstance(key, AppliedPredicate):
264
+ newexprs |= set(key.arguments)
265
+
266
+ return newexprs - exprs, relevant_facts
267
+
268
+
269
+ def get_all_relevant_facts(proposition, assumptions, context,
270
+ use_known_facts=True, iterations=oo):
271
+ """
272
+ Extract all relevant facts from *proposition* and *assumptions*.
273
+
274
+ This function extracts the facts by recursively calling
275
+ ``get_relevant_clsfacts()``. Extracted facts are converted to
276
+ ``EncodedCNF`` and returned.
277
+
278
+ Parameters
279
+ ==========
280
+
281
+ proposition : sympy.assumptions.cnf.CNF
282
+ CNF generated from proposition expression.
283
+
284
+ assumptions : sympy.assumptions.cnf.CNF
285
+ CNF generated from assumption expression.
286
+
287
+ context : sympy.assumptions.cnf.CNF
288
+ CNF generated from assumptions context.
289
+
290
+ use_known_facts : bool, optional.
291
+ If ``True``, facts from ``sympy.assumptions.ask_generated``
292
+ module are encoded as well.
293
+
294
+ iterations : int, optional.
295
+ Number of times that relevant facts are recursively extracted.
296
+ Default is infinite times until no new fact is found.
297
+
298
+ Returns
299
+ =======
300
+
301
+ sympy.assumptions.cnf.EncodedCNF
302
+
303
+ Examples
304
+ ========
305
+
306
+ >>> from sympy import Q
307
+ >>> from sympy.assumptions.cnf import CNF
308
+ >>> from sympy.assumptions.satask import get_all_relevant_facts
309
+ >>> from sympy.abc import x, y
310
+ >>> props = CNF.from_prop(Q.nonzero(x*y))
311
+ >>> assump = CNF.from_prop(Q.nonzero(x))
312
+ >>> context = CNF.from_prop(Q.nonzero(y))
313
+ >>> get_all_relevant_facts(props, assump, context) #doctest: +SKIP
314
+ <sympy.assumptions.cnf.EncodedCNF at 0x7f09faa6ccd0>
315
+
316
+ """
317
+ # The relevant facts might introduce new keys, e.g., Q.zero(x*y) will
318
+ # introduce the keys Q.zero(x) and Q.zero(y), so we need to run it until
319
+ # we stop getting new things. Hopefully this strategy won't lead to an
320
+ # infinite loop in the future.
321
+ i = 0
322
+ relevant_facts = CNF()
323
+ all_exprs = set()
324
+ while True:
325
+ if i == 0:
326
+ exprs = extract_predargs(proposition, assumptions, context)
327
+ all_exprs |= exprs
328
+ exprs, relevant_facts = get_relevant_clsfacts(exprs, relevant_facts)
329
+ i += 1
330
+ if i >= iterations:
331
+ break
332
+ if not exprs:
333
+ break
334
+
335
+ if use_known_facts:
336
+ known_facts_CNF = CNF()
337
+
338
+ if any(expr.kind == MatrixKind(NumberKind) for expr in all_exprs):
339
+ known_facts_CNF.add_clauses(get_all_known_matrix_facts())
340
+ # check for undefinedKind since kind system isn't fully implemented
341
+ if any(((expr.kind == NumberKind) or (expr.kind == UndefinedKind)) for expr in all_exprs):
342
+ known_facts_CNF.add_clauses(get_all_known_number_facts())
343
+
344
+ kf_encoded = EncodedCNF()
345
+ kf_encoded.from_cnf(known_facts_CNF)
346
+
347
+ def translate_literal(lit, delta):
348
+ if lit > 0:
349
+ return lit + delta
350
+ else:
351
+ return lit - delta
352
+
353
+ def translate_data(data, delta):
354
+ return [{translate_literal(i, delta) for i in clause} for clause in data]
355
+ data = []
356
+ symbols = []
357
+ n_lit = len(kf_encoded.symbols)
358
+ for i, expr in enumerate(all_exprs):
359
+ symbols += [pred(expr) for pred in kf_encoded.symbols]
360
+ data += translate_data(kf_encoded.data, i * n_lit)
361
+
362
+ encoding = dict(list(zip(symbols, range(1, len(symbols)+1))))
363
+ ctx = EncodedCNF(data, encoding)
364
+ else:
365
+ ctx = EncodedCNF()
366
+
367
+ ctx.add_from_cnf(relevant_facts)
368
+
369
+ return ctx
.venv/lib/python3.13/site-packages/sympy/assumptions/sathandlers.py ADDED
@@ -0,0 +1,322 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from collections import defaultdict
2
+
3
+ from sympy.assumptions.ask import Q
4
+ from sympy.core import (Add, Mul, Pow, Number, NumberSymbol, Symbol)
5
+ from sympy.core.numbers import ImaginaryUnit
6
+ from sympy.functions.elementary.complexes import Abs
7
+ from sympy.logic.boolalg import (Equivalent, And, Or, Implies)
8
+ from sympy.matrices.expressions import MatMul
9
+
10
+ # APIs here may be subject to change
11
+
12
+
13
+ ### Helper functions ###
14
+
15
+ def allargs(symbol, fact, expr):
16
+ """
17
+ Apply all arguments of the expression to the fact structure.
18
+
19
+ Parameters
20
+ ==========
21
+
22
+ symbol : Symbol
23
+ A placeholder symbol.
24
+
25
+ fact : Boolean
26
+ Resulting ``Boolean`` expression.
27
+
28
+ expr : Expr
29
+
30
+ Examples
31
+ ========
32
+
33
+ >>> from sympy import Q
34
+ >>> from sympy.assumptions.sathandlers import allargs
35
+ >>> from sympy.abc import x, y
36
+ >>> allargs(x, Q.negative(x) | Q.positive(x), x*y)
37
+ (Q.negative(x) | Q.positive(x)) & (Q.negative(y) | Q.positive(y))
38
+
39
+ """
40
+ return And(*[fact.subs(symbol, arg) for arg in expr.args])
41
+
42
+
43
+ def anyarg(symbol, fact, expr):
44
+ """
45
+ Apply any argument of the expression to the fact structure.
46
+
47
+ Parameters
48
+ ==========
49
+
50
+ symbol : Symbol
51
+ A placeholder symbol.
52
+
53
+ fact : Boolean
54
+ Resulting ``Boolean`` expression.
55
+
56
+ expr : Expr
57
+
58
+ Examples
59
+ ========
60
+
61
+ >>> from sympy import Q
62
+ >>> from sympy.assumptions.sathandlers import anyarg
63
+ >>> from sympy.abc import x, y
64
+ >>> anyarg(x, Q.negative(x) & Q.positive(x), x*y)
65
+ (Q.negative(x) & Q.positive(x)) | (Q.negative(y) & Q.positive(y))
66
+
67
+ """
68
+ return Or(*[fact.subs(symbol, arg) for arg in expr.args])
69
+
70
+
71
+ def exactlyonearg(symbol, fact, expr):
72
+ """
73
+ Apply exactly one argument of the expression to the fact structure.
74
+
75
+ Parameters
76
+ ==========
77
+
78
+ symbol : Symbol
79
+ A placeholder symbol.
80
+
81
+ fact : Boolean
82
+ Resulting ``Boolean`` expression.
83
+
84
+ expr : Expr
85
+
86
+ Examples
87
+ ========
88
+
89
+ >>> from sympy import Q
90
+ >>> from sympy.assumptions.sathandlers import exactlyonearg
91
+ >>> from sympy.abc import x, y
92
+ >>> exactlyonearg(x, Q.positive(x), x*y)
93
+ (Q.positive(x) & ~Q.positive(y)) | (Q.positive(y) & ~Q.positive(x))
94
+
95
+ """
96
+ pred_args = [fact.subs(symbol, arg) for arg in expr.args]
97
+ res = Or(*[And(pred_args[i], *[~lit for lit in pred_args[:i] +
98
+ pred_args[i+1:]]) for i in range(len(pred_args))])
99
+ return res
100
+
101
+
102
+ ### Fact registry ###
103
+
104
+ class ClassFactRegistry:
105
+ """
106
+ Register handlers against classes.
107
+
108
+ Explanation
109
+ ===========
110
+
111
+ ``register`` method registers the handler function for a class. Here,
112
+ handler function should return a single fact. ``multiregister`` method
113
+ registers the handler function for multiple classes. Here, handler function
114
+ should return a container of multiple facts.
115
+
116
+ ``registry(expr)`` returns a set of facts for *expr*.
117
+
118
+ Examples
119
+ ========
120
+
121
+ Here, we register the facts for ``Abs``.
122
+
123
+ >>> from sympy import Abs, Equivalent, Q
124
+ >>> from sympy.assumptions.sathandlers import ClassFactRegistry
125
+ >>> reg = ClassFactRegistry()
126
+ >>> @reg.register(Abs)
127
+ ... def f1(expr):
128
+ ... return Q.nonnegative(expr)
129
+ >>> @reg.register(Abs)
130
+ ... def f2(expr):
131
+ ... arg = expr.args[0]
132
+ ... return Equivalent(~Q.zero(arg), ~Q.zero(expr))
133
+
134
+ Calling the registry with expression returns the defined facts for the
135
+ expression.
136
+
137
+ >>> from sympy.abc import x
138
+ >>> reg(Abs(x))
139
+ {Q.nonnegative(Abs(x)), Equivalent(~Q.zero(x), ~Q.zero(Abs(x)))}
140
+
141
+ Multiple facts can be registered at once by ``multiregister`` method.
142
+
143
+ >>> reg2 = ClassFactRegistry()
144
+ >>> @reg2.multiregister(Abs)
145
+ ... def _(expr):
146
+ ... arg = expr.args[0]
147
+ ... return [Q.even(arg) >> Q.even(expr), Q.odd(arg) >> Q.odd(expr)]
148
+ >>> reg2(Abs(x))
149
+ {Implies(Q.even(x), Q.even(Abs(x))), Implies(Q.odd(x), Q.odd(Abs(x)))}
150
+
151
+ """
152
+ def __init__(self):
153
+ self.singlefacts = defaultdict(frozenset)
154
+ self.multifacts = defaultdict(frozenset)
155
+
156
+ def register(self, cls):
157
+ def _(func):
158
+ self.singlefacts[cls] |= {func}
159
+ return func
160
+ return _
161
+
162
+ def multiregister(self, *classes):
163
+ def _(func):
164
+ for cls in classes:
165
+ self.multifacts[cls] |= {func}
166
+ return func
167
+ return _
168
+
169
+ def __getitem__(self, key):
170
+ ret1 = self.singlefacts[key]
171
+ for k in self.singlefacts:
172
+ if issubclass(key, k):
173
+ ret1 |= self.singlefacts[k]
174
+
175
+ ret2 = self.multifacts[key]
176
+ for k in self.multifacts:
177
+ if issubclass(key, k):
178
+ ret2 |= self.multifacts[k]
179
+
180
+ return ret1, ret2
181
+
182
+ def __call__(self, expr):
183
+ ret = set()
184
+
185
+ handlers1, handlers2 = self[type(expr)]
186
+
187
+ ret.update(h(expr) for h in handlers1)
188
+ for h in handlers2:
189
+ ret.update(h(expr))
190
+ return ret
191
+
192
+ class_fact_registry = ClassFactRegistry()
193
+
194
+
195
+
196
+ ### Class fact registration ###
197
+
198
+ x = Symbol('x')
199
+
200
+ ## Abs ##
201
+
202
+ @class_fact_registry.multiregister(Abs)
203
+ def _(expr):
204
+ arg = expr.args[0]
205
+ return [Q.nonnegative(expr),
206
+ Equivalent(~Q.zero(arg), ~Q.zero(expr)),
207
+ Q.even(arg) >> Q.even(expr),
208
+ Q.odd(arg) >> Q.odd(expr),
209
+ Q.integer(arg) >> Q.integer(expr),
210
+ ]
211
+
212
+
213
+ ### Add ##
214
+
215
+ @class_fact_registry.multiregister(Add)
216
+ def _(expr):
217
+ return [allargs(x, Q.positive(x), expr) >> Q.positive(expr),
218
+ allargs(x, Q.negative(x), expr) >> Q.negative(expr),
219
+ allargs(x, Q.real(x), expr) >> Q.real(expr),
220
+ allargs(x, Q.rational(x), expr) >> Q.rational(expr),
221
+ allargs(x, Q.integer(x), expr) >> Q.integer(expr),
222
+ exactlyonearg(x, ~Q.integer(x), expr) >> ~Q.integer(expr),
223
+ ]
224
+
225
+ @class_fact_registry.register(Add)
226
+ def _(expr):
227
+ allargs_real = allargs(x, Q.real(x), expr)
228
+ onearg_irrational = exactlyonearg(x, Q.irrational(x), expr)
229
+ return Implies(allargs_real, Implies(onearg_irrational, Q.irrational(expr)))
230
+
231
+
232
+ ### Mul ###
233
+
234
+ @class_fact_registry.multiregister(Mul)
235
+ def _(expr):
236
+ return [Equivalent(Q.zero(expr), anyarg(x, Q.zero(x), expr)),
237
+ allargs(x, Q.positive(x), expr) >> Q.positive(expr),
238
+ allargs(x, Q.real(x), expr) >> Q.real(expr),
239
+ allargs(x, Q.rational(x), expr) >> Q.rational(expr),
240
+ allargs(x, Q.integer(x), expr) >> Q.integer(expr),
241
+ exactlyonearg(x, ~Q.rational(x), expr) >> ~Q.integer(expr),
242
+ allargs(x, Q.commutative(x), expr) >> Q.commutative(expr),
243
+ ]
244
+
245
+ @class_fact_registry.register(Mul)
246
+ def _(expr):
247
+ # Implicitly assumes Mul has more than one arg
248
+ # Would be allargs(x, Q.prime(x) | Q.composite(x)) except 1 is composite
249
+ # More advanced prime assumptions will require inequalities, as 1 provides
250
+ # a corner case.
251
+ allargs_prime = allargs(x, Q.prime(x), expr)
252
+ return Implies(allargs_prime, ~Q.prime(expr))
253
+
254
+ @class_fact_registry.register(Mul)
255
+ def _(expr):
256
+ # General Case: Odd number of imaginary args implies mul is imaginary(To be implemented)
257
+ allargs_imag_or_real = allargs(x, Q.imaginary(x) | Q.real(x), expr)
258
+ onearg_imaginary = exactlyonearg(x, Q.imaginary(x), expr)
259
+ return Implies(allargs_imag_or_real, Implies(onearg_imaginary, Q.imaginary(expr)))
260
+
261
+ @class_fact_registry.register(Mul)
262
+ def _(expr):
263
+ allargs_real = allargs(x, Q.real(x), expr)
264
+ onearg_irrational = exactlyonearg(x, Q.irrational(x), expr)
265
+ return Implies(allargs_real, Implies(onearg_irrational, Q.irrational(expr)))
266
+
267
+ @class_fact_registry.register(Mul)
268
+ def _(expr):
269
+ # Including the integer qualification means we don't need to add any facts
270
+ # for odd, since the assumptions already know that every integer is
271
+ # exactly one of even or odd.
272
+ allargs_integer = allargs(x, Q.integer(x), expr)
273
+ anyarg_even = anyarg(x, Q.even(x), expr)
274
+ return Implies(allargs_integer, Equivalent(anyarg_even, Q.even(expr)))
275
+
276
+
277
+ ### MatMul ###
278
+
279
+ @class_fact_registry.register(MatMul)
280
+ def _(expr):
281
+ allargs_square = allargs(x, Q.square(x), expr)
282
+ allargs_invertible = allargs(x, Q.invertible(x), expr)
283
+ return Implies(allargs_square, Equivalent(Q.invertible(expr), allargs_invertible))
284
+
285
+
286
+ ### Pow ###
287
+
288
+ @class_fact_registry.multiregister(Pow)
289
+ def _(expr):
290
+ base, exp = expr.base, expr.exp
291
+ return [
292
+ (Q.real(base) & Q.even(exp) & Q.nonnegative(exp)) >> Q.nonnegative(expr),
293
+ (Q.nonnegative(base) & Q.odd(exp) & Q.nonnegative(exp)) >> Q.nonnegative(expr),
294
+ (Q.nonpositive(base) & Q.odd(exp) & Q.nonnegative(exp)) >> Q.nonpositive(expr),
295
+ Equivalent(Q.zero(expr), Q.zero(base) & Q.positive(exp))
296
+ ]
297
+
298
+
299
+ ### Numbers ###
300
+
301
+ _old_assump_getters = {
302
+ Q.positive: lambda o: o.is_positive,
303
+ Q.zero: lambda o: o.is_zero,
304
+ Q.negative: lambda o: o.is_negative,
305
+ Q.rational: lambda o: o.is_rational,
306
+ Q.irrational: lambda o: o.is_irrational,
307
+ Q.even: lambda o: o.is_even,
308
+ Q.odd: lambda o: o.is_odd,
309
+ Q.imaginary: lambda o: o.is_imaginary,
310
+ Q.prime: lambda o: o.is_prime,
311
+ Q.composite: lambda o: o.is_composite,
312
+ }
313
+
314
+ @class_fact_registry.multiregister(Number, NumberSymbol, ImaginaryUnit)
315
+ def _(expr):
316
+ ret = []
317
+ for p, getter in _old_assump_getters.items():
318
+ pred = p(expr)
319
+ prop = getter(expr)
320
+ if prop is not None:
321
+ ret.append(Equivalent(pred, prop))
322
+ return ret
.venv/lib/python3.13/site-packages/sympy/assumptions/wrapper.py ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Functions and wrapper object to call assumption property and predicate
3
+ query with same syntax.
4
+
5
+ In SymPy, there are two assumption systems. Old assumption system is
6
+ defined in sympy/core/assumptions, and it can be accessed by attribute
7
+ such as ``x.is_even``. New assumption system is defined in
8
+ sympy/assumptions, and it can be accessed by predicates such as
9
+ ``Q.even(x)``.
10
+
11
+ Old assumption is fast, while new assumptions can freely take local facts.
12
+ In general, old assumption is used in evaluation method and new assumption
13
+ is used in refinement method.
14
+
15
+ In most cases, both evaluation and refinement follow the same process, and
16
+ the only difference is which assumption system is used. This module provides
17
+ ``is_[...]()`` functions and ``AssumptionsWrapper()`` class which allows
18
+ using two systems with same syntax so that parallel code implementation can be
19
+ avoided.
20
+
21
+ Examples
22
+ ========
23
+
24
+ For multiple use, use ``AssumptionsWrapper()``.
25
+
26
+ >>> from sympy import Q, Symbol
27
+ >>> from sympy.assumptions.wrapper import AssumptionsWrapper
28
+ >>> x = Symbol('x')
29
+ >>> _x = AssumptionsWrapper(x, Q.even(x))
30
+ >>> _x.is_integer
31
+ True
32
+ >>> _x.is_odd
33
+ False
34
+
35
+ For single use, use ``is_[...]()`` functions.
36
+
37
+ >>> from sympy.assumptions.wrapper import is_infinite
38
+ >>> a = Symbol('a')
39
+ >>> print(is_infinite(a))
40
+ None
41
+ >>> is_infinite(a, Q.finite(a))
42
+ False
43
+
44
+ """
45
+
46
+ from sympy.assumptions import ask, Q
47
+ from sympy.core.basic import Basic
48
+ from sympy.core.sympify import _sympify
49
+
50
+
51
+ def make_eval_method(fact):
52
+ def getit(self):
53
+ pred = getattr(Q, fact)
54
+ ret = ask(pred(self.expr), self.assumptions)
55
+ return ret
56
+ return getit
57
+
58
+
59
+ # we subclass Basic to use the fact deduction and caching
60
+ class AssumptionsWrapper(Basic):
61
+ """
62
+ Wrapper over ``Basic`` instances to call predicate query by
63
+ ``.is_[...]`` property
64
+
65
+ Parameters
66
+ ==========
67
+
68
+ expr : Basic
69
+
70
+ assumptions : Boolean, optional
71
+
72
+ Examples
73
+ ========
74
+
75
+ >>> from sympy import Q, Symbol
76
+ >>> from sympy.assumptions.wrapper import AssumptionsWrapper
77
+ >>> x = Symbol('x', even=True)
78
+ >>> AssumptionsWrapper(x).is_integer
79
+ True
80
+ >>> y = Symbol('y')
81
+ >>> AssumptionsWrapper(y, Q.even(y)).is_integer
82
+ True
83
+
84
+ With ``AssumptionsWrapper``, both evaluation and refinement can be supported
85
+ by single implementation.
86
+
87
+ >>> from sympy import Function
88
+ >>> class MyAbs(Function):
89
+ ... @classmethod
90
+ ... def eval(cls, x, assumptions=True):
91
+ ... _x = AssumptionsWrapper(x, assumptions)
92
+ ... if _x.is_nonnegative:
93
+ ... return x
94
+ ... if _x.is_negative:
95
+ ... return -x
96
+ ... def _eval_refine(self, assumptions):
97
+ ... return MyAbs.eval(self.args[0], assumptions)
98
+ >>> MyAbs(x)
99
+ MyAbs(x)
100
+ >>> MyAbs(x).refine(Q.positive(x))
101
+ x
102
+ >>> MyAbs(Symbol('y', negative=True))
103
+ -y
104
+
105
+ """
106
+ def __new__(cls, expr, assumptions=None):
107
+ if assumptions is None:
108
+ return expr
109
+ obj = super().__new__(cls, expr, _sympify(assumptions))
110
+ obj.expr = expr
111
+ obj.assumptions = assumptions
112
+ return obj
113
+
114
+ _eval_is_algebraic = make_eval_method("algebraic")
115
+ _eval_is_antihermitian = make_eval_method("antihermitian")
116
+ _eval_is_commutative = make_eval_method("commutative")
117
+ _eval_is_complex = make_eval_method("complex")
118
+ _eval_is_composite = make_eval_method("composite")
119
+ _eval_is_even = make_eval_method("even")
120
+ _eval_is_extended_negative = make_eval_method("extended_negative")
121
+ _eval_is_extended_nonnegative = make_eval_method("extended_nonnegative")
122
+ _eval_is_extended_nonpositive = make_eval_method("extended_nonpositive")
123
+ _eval_is_extended_nonzero = make_eval_method("extended_nonzero")
124
+ _eval_is_extended_positive = make_eval_method("extended_positive")
125
+ _eval_is_extended_real = make_eval_method("extended_real")
126
+ _eval_is_finite = make_eval_method("finite")
127
+ _eval_is_hermitian = make_eval_method("hermitian")
128
+ _eval_is_imaginary = make_eval_method("imaginary")
129
+ _eval_is_infinite = make_eval_method("infinite")
130
+ _eval_is_integer = make_eval_method("integer")
131
+ _eval_is_irrational = make_eval_method("irrational")
132
+ _eval_is_negative = make_eval_method("negative")
133
+ _eval_is_noninteger = make_eval_method("noninteger")
134
+ _eval_is_nonnegative = make_eval_method("nonnegative")
135
+ _eval_is_nonpositive = make_eval_method("nonpositive")
136
+ _eval_is_nonzero = make_eval_method("nonzero")
137
+ _eval_is_odd = make_eval_method("odd")
138
+ _eval_is_polar = make_eval_method("polar")
139
+ _eval_is_positive = make_eval_method("positive")
140
+ _eval_is_prime = make_eval_method("prime")
141
+ _eval_is_rational = make_eval_method("rational")
142
+ _eval_is_real = make_eval_method("real")
143
+ _eval_is_transcendental = make_eval_method("transcendental")
144
+ _eval_is_zero = make_eval_method("zero")
145
+
146
+
147
+ # one shot functions which are faster than AssumptionsWrapper
148
+
149
+ def is_infinite(obj, assumptions=None):
150
+ if assumptions is None:
151
+ return obj.is_infinite
152
+ return ask(Q.infinite(obj), assumptions)
153
+
154
+
155
+ def is_extended_real(obj, assumptions=None):
156
+ if assumptions is None:
157
+ return obj.is_extended_real
158
+ return ask(Q.extended_real(obj), assumptions)
159
+
160
+
161
+ def is_extended_nonnegative(obj, assumptions=None):
162
+ if assumptions is None:
163
+ return obj.is_extended_nonnegative
164
+ return ask(Q.extended_nonnegative(obj), assumptions)
.venv/lib/python3.13/site-packages/sympy/benchmarks/__init__.py ADDED
File without changes
.venv/lib/python3.13/site-packages/sympy/benchmarks/bench_discrete_log.py ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ from time import time
3
+ from sympy.ntheory.residue_ntheory import (discrete_log,
4
+ _discrete_log_trial_mul, _discrete_log_shanks_steps,
5
+ _discrete_log_pollard_rho, _discrete_log_pohlig_hellman)
6
+
7
+
8
+ # Cyclic group (Z/pZ)* with p prime, order p - 1 and generator g
9
+ data_set_1 = [
10
+ # p, p - 1, g
11
+ [191, 190, 19],
12
+ [46639, 46638, 6],
13
+ [14789363, 14789362, 2],
14
+ [4254225211, 4254225210, 2],
15
+ [432751500361, 432751500360, 7],
16
+ [158505390797053, 158505390797052, 2],
17
+ [6575202655312007, 6575202655312006, 5],
18
+ [8430573471995353769, 8430573471995353768, 3],
19
+ [3938471339744997827267, 3938471339744997827266, 2],
20
+ [875260951364705563393093, 875260951364705563393092, 5],
21
+ ]
22
+
23
+
24
+ # Cyclic sub-groups of (Z/nZ)* with prime order p and generator g
25
+ # (n, p are primes and n = 2 * p + 1)
26
+ data_set_2 = [
27
+ # n, p, g
28
+ [227, 113, 3],
29
+ [2447, 1223, 2],
30
+ [24527, 12263, 2],
31
+ [245639, 122819, 2],
32
+ [2456747, 1228373, 3],
33
+ [24567899, 12283949, 3],
34
+ [245679023, 122839511, 2],
35
+ [2456791307, 1228395653, 3],
36
+ [24567913439, 12283956719, 2],
37
+ [245679135407, 122839567703, 2],
38
+ [2456791354763, 1228395677381, 3],
39
+ [24567913550903, 12283956775451, 2],
40
+ [245679135509519, 122839567754759, 2],
41
+ ]
42
+
43
+
44
+ # Cyclic sub-groups of (Z/nZ)* with smooth order o and generator g
45
+ data_set_3 = [
46
+ # n, o, g
47
+ [2**118, 2**116, 3],
48
+ ]
49
+
50
+
51
+ def bench_discrete_log(data_set, algo=None):
52
+ if algo is None:
53
+ f = discrete_log
54
+ elif algo == 'trial':
55
+ f = _discrete_log_trial_mul
56
+ elif algo == 'shanks':
57
+ f = _discrete_log_shanks_steps
58
+ elif algo == 'rho':
59
+ f = _discrete_log_pollard_rho
60
+ elif algo == 'ph':
61
+ f = _discrete_log_pohlig_hellman
62
+ else:
63
+ raise ValueError("Argument 'algo' should be one"
64
+ " of ('trial', 'shanks', 'rho' or 'ph')")
65
+
66
+ for i, data in enumerate(data_set):
67
+ for j, (n, p, g) in enumerate(data):
68
+ t = time()
69
+ l = f(n, pow(g, p - 1, n), g, p)
70
+ t = time() - t
71
+ print('[%02d-%03d] %15.10f' % (i, j, t))
72
+ assert l == p - 1
73
+
74
+
75
+ if __name__ == '__main__':
76
+ algo = sys.argv[1] \
77
+ if len(sys.argv) > 1 else None
78
+ data_set = [
79
+ data_set_1,
80
+ data_set_2,
81
+ data_set_3,
82
+ ]
83
+ bench_discrete_log(data_set, algo)
.venv/lib/python3.13/site-packages/sympy/benchmarks/bench_meijerint.py ADDED
@@ -0,0 +1,261 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # conceal the implicit import from the code quality tester
2
+ from sympy.core.numbers import (oo, pi)
3
+ from sympy.core.symbol import (Symbol, symbols)
4
+ from sympy.functions.elementary.exponential import exp
5
+ from sympy.functions.elementary.miscellaneous import sqrt
6
+ from sympy.functions.special.bessel import besseli
7
+ from sympy.functions.special.gamma_functions import gamma
8
+ from sympy.integrals.integrals import integrate
9
+ from sympy.integrals.transforms import (mellin_transform,
10
+ inverse_fourier_transform, inverse_mellin_transform,
11
+ laplace_transform, inverse_laplace_transform, fourier_transform)
12
+
13
+ LT = laplace_transform
14
+ FT = fourier_transform
15
+ MT = mellin_transform
16
+ IFT = inverse_fourier_transform
17
+ ILT = inverse_laplace_transform
18
+ IMT = inverse_mellin_transform
19
+
20
+ from sympy.abc import x, y
21
+ nu, beta, rho = symbols('nu beta rho')
22
+
23
+ apos, bpos, cpos, dpos, posk, p = symbols('a b c d k p', positive=True)
24
+ k = Symbol('k', real=True)
25
+ negk = Symbol('k', negative=True)
26
+
27
+ mu1, mu2 = symbols('mu1 mu2', real=True, nonzero=True, finite=True)
28
+ sigma1, sigma2 = symbols('sigma1 sigma2', real=True, nonzero=True,
29
+ finite=True, positive=True)
30
+ rate = Symbol('lambda', positive=True)
31
+
32
+
33
+ def normal(x, mu, sigma):
34
+ return 1/sqrt(2*pi*sigma**2)*exp(-(x - mu)**2/2/sigma**2)
35
+
36
+
37
+ def exponential(x, rate):
38
+ return rate*exp(-rate*x)
39
+ alpha, beta = symbols('alpha beta', positive=True)
40
+ betadist = x**(alpha - 1)*(1 + x)**(-alpha - beta)*gamma(alpha + beta) \
41
+ /gamma(alpha)/gamma(beta)
42
+ kint = Symbol('k', integer=True, positive=True)
43
+ chi = 2**(1 - kint/2)*x**(kint - 1)*exp(-x**2/2)/gamma(kint/2)
44
+ chisquared = 2**(-k/2)/gamma(k/2)*x**(k/2 - 1)*exp(-x/2)
45
+ dagum = apos*p/x*(x/bpos)**(apos*p)/(1 + x**apos/bpos**apos)**(p + 1)
46
+ d1, d2 = symbols('d1 d2', positive=True)
47
+ f = sqrt(((d1*x)**d1 * d2**d2)/(d1*x + d2)**(d1 + d2))/x \
48
+ /gamma(d1/2)/gamma(d2/2)*gamma((d1 + d2)/2)
49
+ nupos, sigmapos = symbols('nu sigma', positive=True)
50
+ rice = x/sigmapos**2*exp(-(x**2 + nupos**2)/2/sigmapos**2)*besseli(0, x*
51
+ nupos/sigmapos**2)
52
+ mu = Symbol('mu', real=True)
53
+ laplace = exp(-abs(x - mu)/bpos)/2/bpos
54
+
55
+ u = Symbol('u', polar=True)
56
+ tpos = Symbol('t', positive=True)
57
+
58
+
59
+ def E(expr):
60
+ integrate(expr*exponential(x, rate)*normal(y, mu1, sigma1),
61
+ (x, 0, oo), (y, -oo, oo), meijerg=True)
62
+ integrate(expr*exponential(x, rate)*normal(y, mu1, sigma1),
63
+ (y, -oo, oo), (x, 0, oo), meijerg=True)
64
+
65
+ bench = [
66
+ 'MT(x**nu*Heaviside(x - 1), x, s)',
67
+ 'MT(x**nu*Heaviside(1 - x), x, s)',
68
+ 'MT((1-x)**(beta - 1)*Heaviside(1-x), x, s)',
69
+ 'MT((x-1)**(beta - 1)*Heaviside(x-1), x, s)',
70
+ 'MT((1+x)**(-rho), x, s)',
71
+ 'MT(abs(1-x)**(-rho), x, s)',
72
+ 'MT((1-x)**(beta-1)*Heaviside(1-x) + a*(x-1)**(beta-1)*Heaviside(x-1), x, s)',
73
+ 'MT((x**a-b**a)/(x-b), x, s)',
74
+ 'MT((x**a-bpos**a)/(x-bpos), x, s)',
75
+ 'MT(exp(-x), x, s)',
76
+ 'MT(exp(-1/x), x, s)',
77
+ 'MT(log(x)**4*Heaviside(1-x), x, s)',
78
+ 'MT(log(x)**3*Heaviside(x-1), x, s)',
79
+ 'MT(log(x + 1), x, s)',
80
+ 'MT(log(1/x + 1), x, s)',
81
+ 'MT(log(abs(1 - x)), x, s)',
82
+ 'MT(log(abs(1 - 1/x)), x, s)',
83
+ 'MT(log(x)/(x+1), x, s)',
84
+ 'MT(log(x)**2/(x+1), x, s)',
85
+ 'MT(log(x)/(x+1)**2, x, s)',
86
+ 'MT(erf(sqrt(x)), x, s)',
87
+
88
+ 'MT(besselj(a, 2*sqrt(x)), x, s)',
89
+ 'MT(sin(sqrt(x))*besselj(a, sqrt(x)), x, s)',
90
+ 'MT(cos(sqrt(x))*besselj(a, sqrt(x)), x, s)',
91
+ 'MT(besselj(a, sqrt(x))**2, x, s)',
92
+ 'MT(besselj(a, sqrt(x))*besselj(-a, sqrt(x)), x, s)',
93
+ 'MT(besselj(a - 1, sqrt(x))*besselj(a, sqrt(x)), x, s)',
94
+ 'MT(besselj(a, sqrt(x))*besselj(b, sqrt(x)), x, s)',
95
+ 'MT(besselj(a, sqrt(x))**2 + besselj(-a, sqrt(x))**2, x, s)',
96
+ 'MT(bessely(a, 2*sqrt(x)), x, s)',
97
+ 'MT(sin(sqrt(x))*bessely(a, sqrt(x)), x, s)',
98
+ 'MT(cos(sqrt(x))*bessely(a, sqrt(x)), x, s)',
99
+ 'MT(besselj(a, sqrt(x))*bessely(a, sqrt(x)), x, s)',
100
+ 'MT(besselj(a, sqrt(x))*bessely(b, sqrt(x)), x, s)',
101
+ 'MT(bessely(a, sqrt(x))**2, x, s)',
102
+
103
+ 'MT(besselk(a, 2*sqrt(x)), x, s)',
104
+ 'MT(besselj(a, 2*sqrt(2*sqrt(x)))*besselk(a, 2*sqrt(2*sqrt(x))), x, s)',
105
+ 'MT(besseli(a, sqrt(x))*besselk(a, sqrt(x)), x, s)',
106
+ 'MT(besseli(b, sqrt(x))*besselk(a, sqrt(x)), x, s)',
107
+ 'MT(exp(-x/2)*besselk(a, x/2), x, s)',
108
+
109
+ # later: ILT, IMT
110
+
111
+ 'LT((t-apos)**bpos*exp(-cpos*(t-apos))*Heaviside(t-apos), t, s)',
112
+ 'LT(t**apos, t, s)',
113
+ 'LT(Heaviside(t), t, s)',
114
+ 'LT(Heaviside(t - apos), t, s)',
115
+ 'LT(1 - exp(-apos*t), t, s)',
116
+ 'LT((exp(2*t)-1)*exp(-bpos - t)*Heaviside(t)/2, t, s, noconds=True)',
117
+ 'LT(exp(t), t, s)',
118
+ 'LT(exp(2*t), t, s)',
119
+ 'LT(exp(apos*t), t, s)',
120
+ 'LT(log(t/apos), t, s)',
121
+ 'LT(erf(t), t, s)',
122
+ 'LT(sin(apos*t), t, s)',
123
+ 'LT(cos(apos*t), t, s)',
124
+ 'LT(exp(-apos*t)*sin(bpos*t), t, s)',
125
+ 'LT(exp(-apos*t)*cos(bpos*t), t, s)',
126
+ 'LT(besselj(0, t), t, s, noconds=True)',
127
+ 'LT(besselj(1, t), t, s, noconds=True)',
128
+
129
+ 'FT(Heaviside(1 - abs(2*apos*x)), x, k)',
130
+ 'FT(Heaviside(1-abs(apos*x))*(1-abs(apos*x)), x, k)',
131
+ 'FT(exp(-apos*x)*Heaviside(x), x, k)',
132
+ 'IFT(1/(apos + 2*pi*I*x), x, posk, noconds=False)',
133
+ 'IFT(1/(apos + 2*pi*I*x), x, -posk, noconds=False)',
134
+ 'IFT(1/(apos + 2*pi*I*x), x, negk)',
135
+ 'FT(x*exp(-apos*x)*Heaviside(x), x, k)',
136
+ 'FT(exp(-apos*x)*sin(bpos*x)*Heaviside(x), x, k)',
137
+ 'FT(exp(-apos*x**2), x, k)',
138
+ 'IFT(sqrt(pi/apos)*exp(-(pi*k)**2/apos), k, x)',
139
+ 'FT(exp(-apos*abs(x)), x, k)',
140
+
141
+ 'integrate(normal(x, mu1, sigma1), (x, -oo, oo), meijerg=True)',
142
+ 'integrate(x*normal(x, mu1, sigma1), (x, -oo, oo), meijerg=True)',
143
+ 'integrate(x**2*normal(x, mu1, sigma1), (x, -oo, oo), meijerg=True)',
144
+ 'integrate(x**3*normal(x, mu1, sigma1), (x, -oo, oo), meijerg=True)',
145
+ 'integrate(normal(x, mu1, sigma1)*normal(y, mu2, sigma2),'
146
+ ' (x, -oo, oo), (y, -oo, oo), meijerg=True)',
147
+ 'integrate(x*normal(x, mu1, sigma1)*normal(y, mu2, sigma2),'
148
+ ' (x, -oo, oo), (y, -oo, oo), meijerg=True)',
149
+ 'integrate(y*normal(x, mu1, sigma1)*normal(y, mu2, sigma2),'
150
+ ' (x, -oo, oo), (y, -oo, oo), meijerg=True)',
151
+ 'integrate(x*y*normal(x, mu1, sigma1)*normal(y, mu2, sigma2),'
152
+ ' (x, -oo, oo), (y, -oo, oo), meijerg=True)',
153
+ 'integrate((x+y+1)*normal(x, mu1, sigma1)*normal(y, mu2, sigma2),'
154
+ ' (x, -oo, oo), (y, -oo, oo), meijerg=True)',
155
+ 'integrate((x+y-1)*normal(x, mu1, sigma1)*normal(y, mu2, sigma2),'
156
+ ' (x, -oo, oo), (y, -oo, oo), meijerg=True)',
157
+ 'integrate(x**2*normal(x, mu1, sigma1)*normal(y, mu2, sigma2),'
158
+ ' (x, -oo, oo), (y, -oo, oo), meijerg=True)',
159
+ 'integrate(y**2*normal(x, mu1, sigma1)*normal(y, mu2, sigma2),'
160
+ ' (x, -oo, oo), (y, -oo, oo), meijerg=True)',
161
+ 'integrate(exponential(x, rate), (x, 0, oo), meijerg=True)',
162
+ 'integrate(x*exponential(x, rate), (x, 0, oo), meijerg=True)',
163
+ 'integrate(x**2*exponential(x, rate), (x, 0, oo), meijerg=True)',
164
+ 'E(1)',
165
+ 'E(x*y)',
166
+ 'E(x*y**2)',
167
+ 'E((x+y+1)**2)',
168
+ 'E(x+y+1)',
169
+ 'E((x+y-1)**2)',
170
+ 'integrate(betadist, (x, 0, oo), meijerg=True)',
171
+ 'integrate(x*betadist, (x, 0, oo), meijerg=True)',
172
+ 'integrate(x**2*betadist, (x, 0, oo), meijerg=True)',
173
+ 'integrate(chi, (x, 0, oo), meijerg=True)',
174
+ 'integrate(x*chi, (x, 0, oo), meijerg=True)',
175
+ 'integrate(x**2*chi, (x, 0, oo), meijerg=True)',
176
+ 'integrate(chisquared, (x, 0, oo), meijerg=True)',
177
+ 'integrate(x*chisquared, (x, 0, oo), meijerg=True)',
178
+ 'integrate(x**2*chisquared, (x, 0, oo), meijerg=True)',
179
+ 'integrate(((x-k)/sqrt(2*k))**3*chisquared, (x, 0, oo), meijerg=True)',
180
+ 'integrate(dagum, (x, 0, oo), meijerg=True)',
181
+ 'integrate(x*dagum, (x, 0, oo), meijerg=True)',
182
+ 'integrate(x**2*dagum, (x, 0, oo), meijerg=True)',
183
+ 'integrate(f, (x, 0, oo), meijerg=True)',
184
+ 'integrate(x*f, (x, 0, oo), meijerg=True)',
185
+ 'integrate(x**2*f, (x, 0, oo), meijerg=True)',
186
+ 'integrate(rice, (x, 0, oo), meijerg=True)',
187
+ 'integrate(laplace, (x, -oo, oo), meijerg=True)',
188
+ 'integrate(x*laplace, (x, -oo, oo), meijerg=True)',
189
+ 'integrate(x**2*laplace, (x, -oo, oo), meijerg=True)',
190
+ 'integrate(log(x) * x**(k-1) * exp(-x) / gamma(k), (x, 0, oo))',
191
+
192
+ 'integrate(sin(z*x)*(x**2-1)**(-(y+S(1)/2)), (x, 1, oo), meijerg=True)',
193
+ 'integrate(besselj(0,x)*besselj(1,x)*exp(-x**2), (x, 0, oo), meijerg=True)',
194
+ 'integrate(besselj(0,x)*besselj(1,x)*besselk(0,x), (x, 0, oo), meijerg=True)',
195
+ 'integrate(besselj(0,x)*besselj(1,x)*exp(-x**2), (x, 0, oo), meijerg=True)',
196
+ 'integrate(besselj(a,x)*besselj(b,x)/x, (x,0,oo), meijerg=True)',
197
+
198
+ 'hyperexpand(meijerg((-s - a/2 + 1, -s + a/2 + 1), (-a/2 - S(1)/2, -s + a/2 + S(3)/2), (a/2, -a/2), (-a/2 - S(1)/2, -s + a/2 + S(3)/2), 1))',
199
+ "gammasimp(S('2**(2*s)*(-pi*gamma(-a + 1)*gamma(a + 1)*gamma(-a - s + 1)*gamma(-a + s - 1/2)*gamma(a - s + 3/2)*gamma(a + s + 1)/(a*(a + s)) - gamma(-a - 1/2)*gamma(-a + 1)*gamma(a + 1)*gamma(a + 3/2)*gamma(-s + 3/2)*gamma(s - 1/2)*gamma(-a + s + 1)*gamma(a - s + 1)/(a*(-a + s)))*gamma(-2*s + 1)*gamma(s + 1)/(pi*s*gamma(-a - 1/2)*gamma(a + 3/2)*gamma(-s + 1)*gamma(-s + 3/2)*gamma(s - 1/2)*gamma(-a - s + 1)*gamma(-a + s - 1/2)*gamma(a - s + 1)*gamma(a - s + 3/2))'))",
200
+
201
+ 'mellin_transform(E1(x), x, s)',
202
+ 'inverse_mellin_transform(gamma(s)/s, s, x, (0, oo))',
203
+ 'mellin_transform(expint(a, x), x, s)',
204
+ 'mellin_transform(Si(x), x, s)',
205
+ 'inverse_mellin_transform(-2**s*sqrt(pi)*gamma((s + 1)/2)/(2*s*gamma(-s/2 + 1)), s, x, (-1, 0))',
206
+ 'mellin_transform(Ci(sqrt(x)), x, s)',
207
+ 'inverse_mellin_transform(-4**s*sqrt(pi)*gamma(s)/(2*s*gamma(-s + S(1)/2)),s, u, (0, 1))',
208
+ 'laplace_transform(Ci(x), x, s)',
209
+ 'laplace_transform(expint(a, x), x, s)',
210
+ 'laplace_transform(expint(1, x), x, s)',
211
+ 'laplace_transform(expint(2, x), x, s)',
212
+ 'inverse_laplace_transform(-log(1 + s**2)/2/s, s, u)',
213
+ 'inverse_laplace_transform(log(s + 1)/s, s, x)',
214
+ 'inverse_laplace_transform((s - log(s + 1))/s**2, s, x)',
215
+ 'laplace_transform(Chi(x), x, s)',
216
+ 'laplace_transform(Shi(x), x, s)',
217
+
218
+ 'integrate(exp(-z*x)/x, (x, 1, oo), meijerg=True, conds="none")',
219
+ 'integrate(exp(-z*x)/x**2, (x, 1, oo), meijerg=True, conds="none")',
220
+ 'integrate(exp(-z*x)/x**3, (x, 1, oo), meijerg=True,conds="none")',
221
+ 'integrate(-cos(x)/x, (x, tpos, oo), meijerg=True)',
222
+ 'integrate(-sin(x)/x, (x, tpos, oo), meijerg=True)',
223
+ 'integrate(sin(x)/x, (x, 0, z), meijerg=True)',
224
+ 'integrate(sinh(x)/x, (x, 0, z), meijerg=True)',
225
+ 'integrate(exp(-x)/x, x, meijerg=True)',
226
+ 'integrate(exp(-x)/x**2, x, meijerg=True)',
227
+ 'integrate(cos(u)/u, u, meijerg=True)',
228
+ 'integrate(cosh(u)/u, u, meijerg=True)',
229
+ 'integrate(expint(1, x), x, meijerg=True)',
230
+ 'integrate(expint(2, x), x, meijerg=True)',
231
+ 'integrate(Si(x), x, meijerg=True)',
232
+ 'integrate(Ci(u), u, meijerg=True)',
233
+ 'integrate(Shi(x), x, meijerg=True)',
234
+ 'integrate(Chi(u), u, meijerg=True)',
235
+ 'integrate(Si(x)*exp(-x), (x, 0, oo), meijerg=True)',
236
+ 'integrate(expint(1, x)*sin(x), (x, 0, oo), meijerg=True)'
237
+ ]
238
+
239
+ from time import time
240
+ from sympy.core.cache import clear_cache
241
+ import sys
242
+
243
+ timings = []
244
+
245
+ if __name__ == '__main__':
246
+ for n, string in enumerate(bench):
247
+ clear_cache()
248
+ _t = time()
249
+ exec(string)
250
+ _t = time() - _t
251
+ timings += [(_t, string)]
252
+ sys.stdout.write('.')
253
+ sys.stdout.flush()
254
+ if n % (len(bench) // 10) == 0:
255
+ sys.stdout.write('%s' % (10*n // len(bench)))
256
+ print()
257
+
258
+ timings.sort(key=lambda x: -x[0])
259
+
260
+ for ti, string in timings:
261
+ print('%.2fs %s' % (ti, string))
.venv/lib/python3.13/site-packages/sympy/benchmarks/bench_symbench.py ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python
2
+ from sympy.core.random import random
3
+ from sympy.core.numbers import (I, Integer, pi)
4
+ from sympy.core.symbol import Symbol
5
+ from sympy.core.sympify import sympify
6
+ from sympy.functions.elementary.miscellaneous import sqrt
7
+ from sympy.functions.elementary.trigonometric import sin
8
+ from sympy.polys.polytools import factor
9
+ from sympy.simplify.simplify import simplify
10
+ from sympy.abc import x, y, z
11
+ from timeit import default_timer as clock
12
+
13
+
14
+ def bench_R1():
15
+ "real(f(f(f(f(f(f(f(f(f(f(i/2)))))))))))"
16
+ def f(z):
17
+ return sqrt(Integer(1)/3)*z**2 + I/3
18
+ f(f(f(f(f(f(f(f(f(f(I/2)))))))))).as_real_imag()[0]
19
+
20
+
21
+ def bench_R2():
22
+ "Hermite polynomial hermite(15, y)"
23
+ def hermite(n, y):
24
+ if n == 1:
25
+ return 2*y
26
+ if n == 0:
27
+ return 1
28
+ return (2*y*hermite(n - 1, y) - 2*(n - 1)*hermite(n - 2, y)).expand()
29
+
30
+ hermite(15, y)
31
+
32
+
33
+ def bench_R3():
34
+ "a = [bool(f==f) for _ in range(10)]"
35
+ f = x + y + z
36
+ [bool(f == f) for _ in range(10)]
37
+
38
+
39
+ def bench_R4():
40
+ # we don't have Tuples
41
+ pass
42
+
43
+
44
+ def bench_R5():
45
+ "blowup(L, 8); L=uniq(L)"
46
+ def blowup(L, n):
47
+ for i in range(n):
48
+ L.append( (L[i] + L[i + 1]) * L[i + 2] )
49
+
50
+ def uniq(x):
51
+ v = set(x)
52
+ return v
53
+ L = [x, y, z]
54
+ blowup(L, 8)
55
+ L = uniq(L)
56
+
57
+
58
+ def bench_R6():
59
+ "sum(simplify((x+sin(i))/x+(x-sin(i))/x) for i in range(100))"
60
+ sum(simplify((x + sin(i))/x + (x - sin(i))/x) for i in range(100))
61
+
62
+
63
+ def bench_R7():
64
+ "[f.subs(x, random()) for _ in range(10**4)]"
65
+ f = x**24 + 34*x**12 + 45*x**3 + 9*x**18 + 34*x**10 + 32*x**21
66
+ [f.subs(x, random()) for _ in range(10**4)]
67
+
68
+
69
+ def bench_R8():
70
+ "right(x^2,0,5,10^4)"
71
+ def right(f, a, b, n):
72
+ a = sympify(a)
73
+ b = sympify(b)
74
+ n = sympify(n)
75
+ x = f.atoms(Symbol).pop()
76
+ Deltax = (b - a)/n
77
+ c = a
78
+ est = 0
79
+ for i in range(n):
80
+ c += Deltax
81
+ est += f.subs(x, c)
82
+ return est*Deltax
83
+
84
+ right(x**2, 0, 5, 10**4)
85
+
86
+
87
+ def _bench_R9():
88
+ "factor(x^20 - pi^5*y^20)"
89
+ factor(x**20 - pi**5*y**20)
90
+
91
+
92
+ def bench_R10():
93
+ "v = [-pi,-pi+1/10..,pi]"
94
+ def srange(min, max, step):
95
+ v = [min]
96
+ while (max - v[-1]).evalf() > 0:
97
+ v.append(v[-1] + step)
98
+ return v[:-1]
99
+ srange(-pi, pi, sympify(1)/10)
100
+
101
+
102
+ def bench_R11():
103
+ "a = [random() + random()*I for w in [0..1000]]"
104
+ [random() + random()*I for w in range(1000)]
105
+
106
+
107
+ def bench_S1():
108
+ "e=(x+y+z+1)**7;f=e*(e+1);f.expand()"
109
+ e = (x + y + z + 1)**7
110
+ f = e*(e + 1)
111
+ f.expand()
112
+
113
+
114
+ if __name__ == '__main__':
115
+ benchmarks = [
116
+ bench_R1,
117
+ bench_R2,
118
+ bench_R3,
119
+ bench_R5,
120
+ bench_R6,
121
+ bench_R7,
122
+ bench_R8,
123
+ #_bench_R9,
124
+ bench_R10,
125
+ bench_R11,
126
+ #bench_S1,
127
+ ]
128
+
129
+ report = []
130
+ for b in benchmarks:
131
+ t = clock()
132
+ b()
133
+ t = clock() - t
134
+ print("%s%65s: %f" % (b.__name__, b.__doc__, t))
.venv/lib/python3.13/site-packages/sympy/codegen/__init__.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """ The ``sympy.codegen`` module contains classes and functions for building
2
+ abstract syntax trees of algorithms. These trees may then be printed by the
3
+ code-printers in ``sympy.printing``.
4
+
5
+ There are several submodules available:
6
+ - ``sympy.codegen.ast``: AST nodes useful across multiple languages.
7
+ - ``sympy.codegen.cnodes``: AST nodes useful for the C family of languages.
8
+ - ``sympy.codegen.fnodes``: AST nodes useful for Fortran.
9
+ - ``sympy.codegen.cfunctions``: functions specific to C (C99 math functions)
10
+ - ``sympy.codegen.ffunctions``: functions specific to Fortran (e.g. ``kind``).
11
+
12
+
13
+
14
+ """
15
+ from .ast import (
16
+ Assignment, aug_assign, CodeBlock, For, Attribute, Variable, Declaration,
17
+ While, Scope, Print, FunctionPrototype, FunctionDefinition, FunctionCall
18
+ )
19
+
20
+ __all__ = [
21
+ 'Assignment', 'aug_assign', 'CodeBlock', 'For', 'Attribute', 'Variable',
22
+ 'Declaration', 'While', 'Scope', 'Print', 'FunctionPrototype',
23
+ 'FunctionDefinition', 'FunctionCall',
24
+ ]
.venv/lib/python3.13/site-packages/sympy/codegen/abstract_nodes.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """This module provides containers for python objects that are valid
2
+ printing targets but are not a subclass of SymPy's Printable.
3
+ """
4
+
5
+
6
+ from sympy.core.containers import Tuple
7
+
8
+
9
+ class List(Tuple):
10
+ """Represents a (frozen) (Python) list (for code printing purposes)."""
11
+ def __eq__(self, other):
12
+ if isinstance(other, list):
13
+ return self == List(*other)
14
+ else:
15
+ return self.args == other
16
+
17
+ def __hash__(self):
18
+ return super().__hash__()
.venv/lib/python3.13/site-packages/sympy/codegen/algorithms.py ADDED
@@ -0,0 +1,180 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sympy.core.containers import Tuple
2
+ from sympy.core.numbers import oo
3
+ from sympy.core.relational import (Gt, Lt)
4
+ from sympy.core.symbol import (Dummy, Symbol)
5
+ from sympy.functions.elementary.complexes import Abs
6
+ from sympy.functions.elementary.miscellaneous import Min, Max
7
+ from sympy.logic.boolalg import And
8
+ from sympy.codegen.ast import (
9
+ Assignment, AddAugmentedAssignment, break_, CodeBlock, Declaration, FunctionDefinition,
10
+ Print, Return, Scope, While, Variable, Pointer, real
11
+ )
12
+ from sympy.codegen.cfunctions import isnan
13
+
14
+ """ This module collects functions for constructing ASTs representing algorithms. """
15
+
16
+ def newtons_method(expr, wrt, atol=1e-12, delta=None, *, rtol=4e-16, debug=False,
17
+ itermax=None, counter=None, delta_fn=lambda e, x: -e/e.diff(x),
18
+ cse=False, handle_nan=None,
19
+ bounds=None):
20
+ """ Generates an AST for Newton-Raphson method (a root-finding algorithm).
21
+
22
+ Explanation
23
+ ===========
24
+
25
+ Returns an abstract syntax tree (AST) based on ``sympy.codegen.ast`` for Netwon's
26
+ method of root-finding.
27
+
28
+ Parameters
29
+ ==========
30
+
31
+ expr : expression
32
+ wrt : Symbol
33
+ With respect to, i.e. what is the variable.
34
+ atol : number or expression
35
+ Absolute tolerance (stopping criterion)
36
+ rtol : number or expression
37
+ Relative tolerance (stopping criterion)
38
+ delta : Symbol
39
+ Will be a ``Dummy`` if ``None``.
40
+ debug : bool
41
+ Whether to print convergence information during iterations
42
+ itermax : number or expr
43
+ Maximum number of iterations.
44
+ counter : Symbol
45
+ Will be a ``Dummy`` if ``None``.
46
+ delta_fn: Callable[[Expr, Symbol], Expr]
47
+ computes the step, default is newtons method. For e.g. Halley's method
48
+ use delta_fn=lambda e, x: -2*e*e.diff(x)/(2*e.diff(x)**2 - e*e.diff(x, 2))
49
+ cse: bool
50
+ Perform common sub-expression elimination on delta expression
51
+ handle_nan: Token
52
+ How to handle occurrence of not-a-number (NaN).
53
+ bounds: Optional[tuple[Expr, Expr]]
54
+ Perform optimization within bounds
55
+
56
+ Examples
57
+ ========
58
+
59
+ >>> from sympy import symbols, cos
60
+ >>> from sympy.codegen.ast import Assignment
61
+ >>> from sympy.codegen.algorithms import newtons_method
62
+ >>> x, dx, atol = symbols('x dx atol')
63
+ >>> expr = cos(x) - x**3
64
+ >>> algo = newtons_method(expr, x, atol=atol, delta=dx)
65
+ >>> algo.has(Assignment(dx, -expr/expr.diff(x)))
66
+ True
67
+
68
+ References
69
+ ==========
70
+
71
+ .. [1] https://en.wikipedia.org/wiki/Newton%27s_method
72
+
73
+ """
74
+
75
+ if delta is None:
76
+ delta = Dummy()
77
+ Wrapper = Scope
78
+ name_d = 'delta'
79
+ else:
80
+ Wrapper = lambda x: x
81
+ name_d = delta.name
82
+
83
+ delta_expr = delta_fn(expr, wrt)
84
+ if cse:
85
+ from sympy.simplify.cse_main import cse
86
+ cses, (red,) = cse([delta_expr.factor()])
87
+ whl_bdy = [Assignment(dum, sub_e) for dum, sub_e in cses]
88
+ whl_bdy += [Assignment(delta, red)]
89
+ else:
90
+ whl_bdy = [Assignment(delta, delta_expr)]
91
+ if handle_nan is not None:
92
+ whl_bdy += [While(isnan(delta), CodeBlock(handle_nan, break_))]
93
+ whl_bdy += [AddAugmentedAssignment(wrt, delta)]
94
+ if bounds is not None:
95
+ whl_bdy += [Assignment(wrt, Min(Max(wrt, bounds[0]), bounds[1]))]
96
+ if debug:
97
+ prnt = Print([wrt, delta], r"{}=%12.5g {}=%12.5g\n".format(wrt.name, name_d))
98
+ whl_bdy += [prnt]
99
+ req = Gt(Abs(delta), atol + rtol*Abs(wrt))
100
+ declars = [Declaration(Variable(delta, type=real, value=oo))]
101
+ if itermax is not None:
102
+ counter = counter or Dummy(integer=True)
103
+ v_counter = Variable.deduced(counter, 0)
104
+ declars.append(Declaration(v_counter))
105
+ whl_bdy.append(AddAugmentedAssignment(counter, 1))
106
+ req = And(req, Lt(counter, itermax))
107
+ whl = While(req, CodeBlock(*whl_bdy))
108
+ blck = declars
109
+ if debug:
110
+ blck.append(Print([wrt], r"{}=%12.5g\n".format(wrt.name)))
111
+ blck += [whl]
112
+ return Wrapper(CodeBlock(*blck))
113
+
114
+
115
+ def _symbol_of(arg):
116
+ if isinstance(arg, Declaration):
117
+ arg = arg.variable.symbol
118
+ elif isinstance(arg, Variable):
119
+ arg = arg.symbol
120
+ return arg
121
+
122
+
123
+ def newtons_method_function(expr, wrt, params=None, func_name="newton", attrs=Tuple(), *, delta=None, **kwargs):
124
+ """ Generates an AST for a function implementing the Newton-Raphson method.
125
+
126
+ Parameters
127
+ ==========
128
+
129
+ expr : expression
130
+ wrt : Symbol
131
+ With respect to, i.e. what is the variable
132
+ params : iterable of symbols
133
+ Symbols appearing in expr that are taken as constants during the iterations
134
+ (these will be accepted as parameters to the generated function).
135
+ func_name : str
136
+ Name of the generated function.
137
+ attrs : Tuple
138
+ Attribute instances passed as ``attrs`` to ``FunctionDefinition``.
139
+ \\*\\*kwargs :
140
+ Keyword arguments passed to :func:`sympy.codegen.algorithms.newtons_method`.
141
+
142
+ Examples
143
+ ========
144
+
145
+ >>> from sympy import symbols, cos
146
+ >>> from sympy.codegen.algorithms import newtons_method_function
147
+ >>> from sympy.codegen.pyutils import render_as_module
148
+ >>> x = symbols('x')
149
+ >>> expr = cos(x) - x**3
150
+ >>> func = newtons_method_function(expr, x)
151
+ >>> py_mod = render_as_module(func) # source code as string
152
+ >>> namespace = {}
153
+ >>> exec(py_mod, namespace, namespace)
154
+ >>> res = eval('newton(0.5)', namespace)
155
+ >>> abs(res - 0.865474033102) < 1e-12
156
+ True
157
+
158
+ See Also
159
+ ========
160
+
161
+ sympy.codegen.algorithms.newtons_method
162
+
163
+ """
164
+ if params is None:
165
+ params = (wrt,)
166
+ pointer_subs = {p.symbol: Symbol('(*%s)' % p.symbol.name)
167
+ for p in params if isinstance(p, Pointer)}
168
+ if delta is None:
169
+ delta = Symbol('d_' + wrt.name)
170
+ if expr.has(delta):
171
+ delta = None # will use Dummy
172
+ algo = newtons_method(expr, wrt, delta=delta, **kwargs).xreplace(pointer_subs)
173
+ if isinstance(algo, Scope):
174
+ algo = algo.body
175
+ not_in_params = expr.free_symbols.difference({_symbol_of(p) for p in params})
176
+ if not_in_params:
177
+ raise ValueError("Missing symbols in params: %s" % ', '.join(map(str, not_in_params)))
178
+ declars = tuple(Variable(p, real) for p in params)
179
+ body = CodeBlock(algo, Return(wrt))
180
+ return FunctionDefinition(real, func_name, declars, body, attrs=attrs)
.venv/lib/python3.13/site-packages/sympy/codegen/approximations.py ADDED
@@ -0,0 +1,187 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import math
2
+ from sympy.sets.sets import Interval
3
+ from sympy.calculus.singularities import is_increasing, is_decreasing
4
+ from sympy.codegen.rewriting import Optimization
5
+ from sympy.core.function import UndefinedFunction
6
+
7
+ """
8
+ This module collects classes useful for approximate rewriting of expressions.
9
+ This can be beneficial when generating numeric code for which performance is
10
+ of greater importance than precision (e.g. for preconditioners used in iterative
11
+ methods).
12
+ """
13
+
14
+ class SumApprox(Optimization):
15
+ """
16
+ Approximates sum by neglecting small terms.
17
+
18
+ Explanation
19
+ ===========
20
+
21
+ If terms are expressions which can be determined to be monotonic, then
22
+ bounds for those expressions are added.
23
+
24
+ Parameters
25
+ ==========
26
+
27
+ bounds : dict
28
+ Mapping expressions to length 2 tuple of bounds (low, high).
29
+ reltol : number
30
+ Threshold for when to ignore a term. Taken relative to the largest
31
+ lower bound among bounds.
32
+
33
+ Examples
34
+ ========
35
+
36
+ >>> from sympy import exp
37
+ >>> from sympy.abc import x, y, z
38
+ >>> from sympy.codegen.rewriting import optimize
39
+ >>> from sympy.codegen.approximations import SumApprox
40
+ >>> bounds = {x: (-1, 1), y: (1000, 2000), z: (-10, 3)}
41
+ >>> sum_approx3 = SumApprox(bounds, reltol=1e-3)
42
+ >>> sum_approx2 = SumApprox(bounds, reltol=1e-2)
43
+ >>> sum_approx1 = SumApprox(bounds, reltol=1e-1)
44
+ >>> expr = 3*(x + y + exp(z))
45
+ >>> optimize(expr, [sum_approx3])
46
+ 3*(x + y + exp(z))
47
+ >>> optimize(expr, [sum_approx2])
48
+ 3*y + 3*exp(z)
49
+ >>> optimize(expr, [sum_approx1])
50
+ 3*y
51
+
52
+ """
53
+
54
+ def __init__(self, bounds, reltol, **kwargs):
55
+ super().__init__(**kwargs)
56
+ self.bounds = bounds
57
+ self.reltol = reltol
58
+
59
+ def __call__(self, expr):
60
+ return expr.factor().replace(self.query, lambda arg: self.value(arg))
61
+
62
+ def query(self, expr):
63
+ return expr.is_Add
64
+
65
+ def value(self, add):
66
+ for term in add.args:
67
+ if term.is_number or term in self.bounds or len(term.free_symbols) != 1:
68
+ continue
69
+ fs, = term.free_symbols
70
+ if fs not in self.bounds:
71
+ continue
72
+ intrvl = Interval(*self.bounds[fs])
73
+ if is_increasing(term, intrvl, fs):
74
+ self.bounds[term] = (
75
+ term.subs({fs: self.bounds[fs][0]}),
76
+ term.subs({fs: self.bounds[fs][1]})
77
+ )
78
+ elif is_decreasing(term, intrvl, fs):
79
+ self.bounds[term] = (
80
+ term.subs({fs: self.bounds[fs][1]}),
81
+ term.subs({fs: self.bounds[fs][0]})
82
+ )
83
+ else:
84
+ return add
85
+
86
+ if all(term.is_number or term in self.bounds for term in add.args):
87
+ bounds = [(term, term) if term.is_number else self.bounds[term] for term in add.args]
88
+ largest_abs_guarantee = 0
89
+ for lo, hi in bounds:
90
+ if lo <= 0 <= hi:
91
+ continue
92
+ largest_abs_guarantee = max(largest_abs_guarantee,
93
+ min(abs(lo), abs(hi)))
94
+ new_terms = []
95
+ for term, (lo, hi) in zip(add.args, bounds):
96
+ if max(abs(lo), abs(hi)) >= largest_abs_guarantee*self.reltol:
97
+ new_terms.append(term)
98
+ return add.func(*new_terms)
99
+ else:
100
+ return add
101
+
102
+
103
+ class SeriesApprox(Optimization):
104
+ """ Approximates functions by expanding them as a series.
105
+
106
+ Parameters
107
+ ==========
108
+
109
+ bounds : dict
110
+ Mapping expressions to length 2 tuple of bounds (low, high).
111
+ reltol : number
112
+ Threshold for when to ignore a term. Taken relative to the largest
113
+ lower bound among bounds.
114
+ max_order : int
115
+ Largest order to include in series expansion
116
+ n_point_checks : int (even)
117
+ The validity of an expansion (with respect to reltol) is checked at
118
+ discrete points (linearly spaced over the bounds of the variable). The
119
+ number of points used in this numerical check is given by this number.
120
+
121
+ Examples
122
+ ========
123
+
124
+ >>> from sympy import sin, pi
125
+ >>> from sympy.abc import x, y
126
+ >>> from sympy.codegen.rewriting import optimize
127
+ >>> from sympy.codegen.approximations import SeriesApprox
128
+ >>> bounds = {x: (-.1, .1), y: (pi-1, pi+1)}
129
+ >>> series_approx2 = SeriesApprox(bounds, reltol=1e-2)
130
+ >>> series_approx3 = SeriesApprox(bounds, reltol=1e-3)
131
+ >>> series_approx8 = SeriesApprox(bounds, reltol=1e-8)
132
+ >>> expr = sin(x)*sin(y)
133
+ >>> optimize(expr, [series_approx2])
134
+ x*(-y + (y - pi)**3/6 + pi)
135
+ >>> optimize(expr, [series_approx3])
136
+ (-x**3/6 + x)*sin(y)
137
+ >>> optimize(expr, [series_approx8])
138
+ sin(x)*sin(y)
139
+
140
+ """
141
+ def __init__(self, bounds, reltol, max_order=4, n_point_checks=4, **kwargs):
142
+ super().__init__(**kwargs)
143
+ self.bounds = bounds
144
+ self.reltol = reltol
145
+ self.max_order = max_order
146
+ if n_point_checks % 2 == 1:
147
+ raise ValueError("Checking the solution at expansion point is not helpful")
148
+ self.n_point_checks = n_point_checks
149
+ self._prec = math.ceil(-math.log10(self.reltol))
150
+
151
+ def __call__(self, expr):
152
+ return expr.factor().replace(self.query, lambda arg: self.value(arg))
153
+
154
+ def query(self, expr):
155
+ return (expr.is_Function and not isinstance(expr, UndefinedFunction)
156
+ and len(expr.args) == 1)
157
+
158
+ def value(self, fexpr):
159
+ free_symbols = fexpr.free_symbols
160
+ if len(free_symbols) != 1:
161
+ return fexpr
162
+ symb, = free_symbols
163
+ if symb not in self.bounds:
164
+ return fexpr
165
+ lo, hi = self.bounds[symb]
166
+ x0 = (lo + hi)/2
167
+ cheapest = None
168
+ for n in range(self.max_order+1, 0, -1):
169
+ fseri = fexpr.series(symb, x0=x0, n=n).removeO()
170
+ n_ok = True
171
+ for idx in range(self.n_point_checks):
172
+ x = lo + idx*(hi - lo)/(self.n_point_checks - 1)
173
+ val = fseri.xreplace({symb: x})
174
+ ref = fexpr.xreplace({symb: x})
175
+ if abs((1 - val/ref).evalf(self._prec)) > self.reltol:
176
+ n_ok = False
177
+ break
178
+
179
+ if n_ok:
180
+ cheapest = fseri
181
+ else:
182
+ break
183
+
184
+ if cheapest is None:
185
+ return fexpr
186
+ else:
187
+ return cheapest
.venv/lib/python3.13/site-packages/sympy/codegen/ast.py ADDED
@@ -0,0 +1,1906 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Types used to represent a full function/module as an Abstract Syntax Tree.
3
+
4
+ Most types are small, and are merely used as tokens in the AST. A tree diagram
5
+ has been included below to illustrate the relationships between the AST types.
6
+
7
+
8
+ AST Type Tree
9
+ -------------
10
+ ::
11
+
12
+ *Basic*
13
+ |
14
+ |
15
+ CodegenAST
16
+ |
17
+ |--->AssignmentBase
18
+ | |--->Assignment
19
+ | |--->AugmentedAssignment
20
+ | |--->AddAugmentedAssignment
21
+ | |--->SubAugmentedAssignment
22
+ | |--->MulAugmentedAssignment
23
+ | |--->DivAugmentedAssignment
24
+ | |--->ModAugmentedAssignment
25
+ |
26
+ |--->CodeBlock
27
+ |
28
+ |
29
+ |--->Token
30
+ |--->Attribute
31
+ |--->For
32
+ |--->String
33
+ | |--->QuotedString
34
+ | |--->Comment
35
+ |--->Type
36
+ | |--->IntBaseType
37
+ | | |--->_SizedIntType
38
+ | | |--->SignedIntType
39
+ | | |--->UnsignedIntType
40
+ | |--->FloatBaseType
41
+ | |--->FloatType
42
+ | |--->ComplexBaseType
43
+ | |--->ComplexType
44
+ |--->Node
45
+ | |--->Variable
46
+ | | |---> Pointer
47
+ | |--->FunctionPrototype
48
+ | |--->FunctionDefinition
49
+ |--->Element
50
+ |--->Declaration
51
+ |--->While
52
+ |--->Scope
53
+ |--->Stream
54
+ |--->Print
55
+ |--->FunctionCall
56
+ |--->BreakToken
57
+ |--->ContinueToken
58
+ |--->NoneToken
59
+ |--->Return
60
+
61
+
62
+ Predefined types
63
+ ----------------
64
+
65
+ A number of ``Type`` instances are provided in the ``sympy.codegen.ast`` module
66
+ for convenience. Perhaps the two most common ones for code-generation (of numeric
67
+ codes) are ``float32`` and ``float64`` (known as single and double precision respectively).
68
+ There are also precision generic versions of Types (for which the codeprinters selects the
69
+ underlying data type at time of printing): ``real``, ``integer``, ``complex_``, ``bool_``.
70
+
71
+ The other ``Type`` instances defined are:
72
+
73
+ - ``intc``: Integer type used by C's "int".
74
+ - ``intp``: Integer type used by C's "unsigned".
75
+ - ``int8``, ``int16``, ``int32``, ``int64``: n-bit integers.
76
+ - ``uint8``, ``uint16``, ``uint32``, ``uint64``: n-bit unsigned integers.
77
+ - ``float80``: known as "extended precision" on modern x86/amd64 hardware.
78
+ - ``complex64``: Complex number represented by two ``float32`` numbers
79
+ - ``complex128``: Complex number represented by two ``float64`` numbers
80
+
81
+ Using the nodes
82
+ ---------------
83
+
84
+ It is possible to construct simple algorithms using the AST nodes. Let's construct a loop applying
85
+ Newton's method::
86
+
87
+ >>> from sympy import symbols, cos
88
+ >>> from sympy.codegen.ast import While, Assignment, aug_assign, Print, QuotedString
89
+ >>> t, dx, x = symbols('tol delta val')
90
+ >>> expr = cos(x) - x**3
91
+ >>> whl = While(abs(dx) > t, [
92
+ ... Assignment(dx, -expr/expr.diff(x)),
93
+ ... aug_assign(x, '+', dx),
94
+ ... Print([x])
95
+ ... ])
96
+ >>> from sympy import pycode
97
+ >>> py_str = pycode(whl)
98
+ >>> print(py_str)
99
+ while (abs(delta) > tol):
100
+ delta = (val**3 - math.cos(val))/(-3*val**2 - math.sin(val))
101
+ val += delta
102
+ print(val)
103
+ >>> import math
104
+ >>> tol, val, delta = 1e-5, 0.5, float('inf')
105
+ >>> exec(py_str)
106
+ 1.1121416371
107
+ 0.909672693737
108
+ 0.867263818209
109
+ 0.865477135298
110
+ 0.865474033111
111
+ >>> print('%3.1g' % (math.cos(val) - val**3))
112
+ -3e-11
113
+
114
+ If we want to generate Fortran code for the same while loop we simple call ``fcode``::
115
+
116
+ >>> from sympy import fcode
117
+ >>> print(fcode(whl, standard=2003, source_format='free'))
118
+ do while (abs(delta) > tol)
119
+ delta = (val**3 - cos(val))/(-3*val**2 - sin(val))
120
+ val = val + delta
121
+ print *, val
122
+ end do
123
+
124
+ There is a function constructing a loop (or a complete function) like this in
125
+ :mod:`sympy.codegen.algorithms`.
126
+
127
+ """
128
+
129
+ from __future__ import annotations
130
+ from typing import Any
131
+
132
+ from collections import defaultdict
133
+
134
+ from sympy.core.relational import (Ge, Gt, Le, Lt)
135
+ from sympy.core import Symbol, Tuple, Dummy
136
+ from sympy.core.basic import Basic
137
+ from sympy.core.expr import Expr, Atom
138
+ from sympy.core.numbers import Float, Integer, oo
139
+ from sympy.core.sympify import _sympify, sympify, SympifyError
140
+ from sympy.utilities.iterables import (iterable, topological_sort,
141
+ numbered_symbols, filter_symbols)
142
+
143
+
144
+ def _mk_Tuple(args):
145
+ """
146
+ Create a SymPy Tuple object from an iterable, converting Python strings to
147
+ AST strings.
148
+
149
+ Parameters
150
+ ==========
151
+
152
+ args: iterable
153
+ Arguments to :class:`sympy.Tuple`.
154
+
155
+ Returns
156
+ =======
157
+
158
+ sympy.Tuple
159
+ """
160
+ args = [String(arg) if isinstance(arg, str) else arg for arg in args]
161
+ return Tuple(*args)
162
+
163
+
164
+ class CodegenAST(Basic):
165
+ __slots__ = ()
166
+
167
+
168
+ class Token(CodegenAST):
169
+ """ Base class for the AST types.
170
+
171
+ Explanation
172
+ ===========
173
+
174
+ Defining fields are set in ``_fields``. Attributes (defined in _fields)
175
+ are only allowed to contain instances of Basic (unless atomic, see
176
+ ``String``). The arguments to ``__new__()`` correspond to the attributes in
177
+ the order defined in ``_fields`. The ``defaults`` class attribute is a
178
+ dictionary mapping attribute names to their default values.
179
+
180
+ Subclasses should not need to override the ``__new__()`` method. They may
181
+ define a class or static method named ``_construct_<attr>`` for each
182
+ attribute to process the value passed to ``__new__()``. Attributes listed
183
+ in the class attribute ``not_in_args`` are not passed to :class:`~.Basic`.
184
+ """
185
+
186
+ __slots__: tuple[str, ...] = ()
187
+ _fields = __slots__
188
+ defaults: dict[str, Any] = {}
189
+ not_in_args: list[str] = []
190
+ indented_args = ['body']
191
+
192
+ @property
193
+ def is_Atom(self):
194
+ return len(self._fields) == 0
195
+
196
+ @classmethod
197
+ def _get_constructor(cls, attr):
198
+ """ Get the constructor function for an attribute by name. """
199
+ return getattr(cls, '_construct_%s' % attr, lambda x: x)
200
+
201
+ @classmethod
202
+ def _construct(cls, attr, arg):
203
+ """ Construct an attribute value from argument passed to ``__new__()``. """
204
+ # arg may be ``NoneToken()``, so comparison is done using == instead of ``is`` operator
205
+ if arg == None:
206
+ return cls.defaults.get(attr, none)
207
+ else:
208
+ if isinstance(arg, Dummy): # SymPy's replace uses Dummy instances
209
+ return arg
210
+ else:
211
+ return cls._get_constructor(attr)(arg)
212
+
213
+ def __new__(cls, *args, **kwargs):
214
+ # Pass through existing instances when given as sole argument
215
+ if len(args) == 1 and not kwargs and isinstance(args[0], cls):
216
+ return args[0]
217
+
218
+ if len(args) > len(cls._fields):
219
+ raise ValueError("Too many arguments (%d), expected at most %d" % (len(args), len(cls._fields)))
220
+
221
+ attrvals = []
222
+
223
+ # Process positional arguments
224
+ for attrname, argval in zip(cls._fields, args):
225
+ if attrname in kwargs:
226
+ raise TypeError('Got multiple values for attribute %r' % attrname)
227
+
228
+ attrvals.append(cls._construct(attrname, argval))
229
+
230
+ # Process keyword arguments
231
+ for attrname in cls._fields[len(args):]:
232
+ if attrname in kwargs:
233
+ argval = kwargs.pop(attrname)
234
+
235
+ elif attrname in cls.defaults:
236
+ argval = cls.defaults[attrname]
237
+
238
+ else:
239
+ raise TypeError('No value for %r given and attribute has no default' % attrname)
240
+
241
+ attrvals.append(cls._construct(attrname, argval))
242
+
243
+ if kwargs:
244
+ raise ValueError("Unknown keyword arguments: %s" % ' '.join(kwargs))
245
+
246
+ # Parent constructor
247
+ basic_args = [
248
+ val for attr, val in zip(cls._fields, attrvals)
249
+ if attr not in cls.not_in_args
250
+ ]
251
+ obj = CodegenAST.__new__(cls, *basic_args)
252
+
253
+ # Set attributes
254
+ for attr, arg in zip(cls._fields, attrvals):
255
+ setattr(obj, attr, arg)
256
+
257
+ return obj
258
+
259
+ def __eq__(self, other):
260
+ if not isinstance(other, self.__class__):
261
+ return False
262
+ for attr in self._fields:
263
+ if getattr(self, attr) != getattr(other, attr):
264
+ return False
265
+ return True
266
+
267
+ def _hashable_content(self):
268
+ return tuple([getattr(self, attr) for attr in self._fields])
269
+
270
+ def __hash__(self):
271
+ return super().__hash__()
272
+
273
+ def _joiner(self, k, indent_level):
274
+ return (',\n' + ' '*indent_level) if k in self.indented_args else ', '
275
+
276
+ def _indented(self, printer, k, v, *args, **kwargs):
277
+ il = printer._context['indent_level']
278
+ def _print(arg):
279
+ if isinstance(arg, Token):
280
+ return printer._print(arg, *args, joiner=self._joiner(k, il), **kwargs)
281
+ else:
282
+ return printer._print(arg, *args, **kwargs)
283
+
284
+ if isinstance(v, Tuple):
285
+ joined = self._joiner(k, il).join([_print(arg) for arg in v.args])
286
+ if k in self.indented_args:
287
+ return '(\n' + ' '*il + joined + ',\n' + ' '*(il - 4) + ')'
288
+ else:
289
+ return ('({0},)' if len(v.args) == 1 else '({0})').format(joined)
290
+ else:
291
+ return _print(v)
292
+
293
+ def _sympyrepr(self, printer, *args, joiner=', ', **kwargs):
294
+ from sympy.printing.printer import printer_context
295
+ exclude = kwargs.get('exclude', ())
296
+ values = [getattr(self, k) for k in self._fields]
297
+ indent_level = printer._context.get('indent_level', 0)
298
+
299
+ arg_reprs = []
300
+
301
+ for i, (attr, value) in enumerate(zip(self._fields, values)):
302
+ if attr in exclude:
303
+ continue
304
+
305
+ # Skip attributes which have the default value
306
+ if attr in self.defaults and value == self.defaults[attr]:
307
+ continue
308
+
309
+ ilvl = indent_level + 4 if attr in self.indented_args else 0
310
+ with printer_context(printer, indent_level=ilvl):
311
+ indented = self._indented(printer, attr, value, *args, **kwargs)
312
+ arg_reprs.append(('{1}' if i == 0 else '{0}={1}').format(attr, indented.lstrip()))
313
+
314
+ return "{}({})".format(self.__class__.__name__, joiner.join(arg_reprs))
315
+
316
+ _sympystr = _sympyrepr
317
+
318
+ def __repr__(self): # sympy.core.Basic.__repr__ uses sstr
319
+ from sympy.printing import srepr
320
+ return srepr(self)
321
+
322
+ def kwargs(self, exclude=(), apply=None):
323
+ """ Get instance's attributes as dict of keyword arguments.
324
+
325
+ Parameters
326
+ ==========
327
+
328
+ exclude : collection of str
329
+ Collection of keywords to exclude.
330
+
331
+ apply : callable, optional
332
+ Function to apply to all values.
333
+ """
334
+ kwargs = {k: getattr(self, k) for k in self._fields if k not in exclude}
335
+ if apply is not None:
336
+ return {k: apply(v) for k, v in kwargs.items()}
337
+ else:
338
+ return kwargs
339
+
340
+ class BreakToken(Token):
341
+ """ Represents 'break' in C/Python ('exit' in Fortran).
342
+
343
+ Use the premade instance ``break_`` or instantiate manually.
344
+
345
+ Examples
346
+ ========
347
+
348
+ >>> from sympy import ccode, fcode
349
+ >>> from sympy.codegen.ast import break_
350
+ >>> ccode(break_)
351
+ 'break'
352
+ >>> fcode(break_, source_format='free')
353
+ 'exit'
354
+ """
355
+
356
+ break_ = BreakToken()
357
+
358
+
359
+ class ContinueToken(Token):
360
+ """ Represents 'continue' in C/Python ('cycle' in Fortran)
361
+
362
+ Use the premade instance ``continue_`` or instantiate manually.
363
+
364
+ Examples
365
+ ========
366
+
367
+ >>> from sympy import ccode, fcode
368
+ >>> from sympy.codegen.ast import continue_
369
+ >>> ccode(continue_)
370
+ 'continue'
371
+ >>> fcode(continue_, source_format='free')
372
+ 'cycle'
373
+ """
374
+
375
+ continue_ = ContinueToken()
376
+
377
+ class NoneToken(Token):
378
+ """ The AST equivalence of Python's NoneType
379
+
380
+ The corresponding instance of Python's ``None`` is ``none``.
381
+
382
+ Examples
383
+ ========
384
+
385
+ >>> from sympy.codegen.ast import none, Variable
386
+ >>> from sympy import pycode
387
+ >>> print(pycode(Variable('x').as_Declaration(value=none)))
388
+ x = None
389
+
390
+ """
391
+ def __eq__(self, other):
392
+ return other is None or isinstance(other, NoneToken)
393
+
394
+ def _hashable_content(self):
395
+ return ()
396
+
397
+ def __hash__(self):
398
+ return super().__hash__()
399
+
400
+
401
+ none = NoneToken()
402
+
403
+
404
+ class AssignmentBase(CodegenAST):
405
+ """ Abstract base class for Assignment and AugmentedAssignment.
406
+
407
+ Attributes:
408
+ ===========
409
+
410
+ op : str
411
+ Symbol for assignment operator, e.g. "=", "+=", etc.
412
+ """
413
+
414
+ def __new__(cls, lhs, rhs):
415
+ lhs = _sympify(lhs)
416
+ rhs = _sympify(rhs)
417
+
418
+ cls._check_args(lhs, rhs)
419
+
420
+ return super().__new__(cls, lhs, rhs)
421
+
422
+ @property
423
+ def lhs(self):
424
+ return self.args[0]
425
+
426
+ @property
427
+ def rhs(self):
428
+ return self.args[1]
429
+
430
+ @classmethod
431
+ def _check_args(cls, lhs, rhs):
432
+ """ Check arguments to __new__ and raise exception if any problems found.
433
+
434
+ Derived classes may wish to override this.
435
+ """
436
+ from sympy.matrices.expressions.matexpr import (
437
+ MatrixElement, MatrixSymbol)
438
+ from sympy.tensor.indexed import Indexed
439
+ from sympy.tensor.array.expressions import ArrayElement
440
+
441
+ # Tuple of things that can be on the lhs of an assignment
442
+ assignable = (Symbol, MatrixSymbol, MatrixElement, Indexed, Element, Variable,
443
+ ArrayElement)
444
+ if not isinstance(lhs, assignable):
445
+ raise TypeError("Cannot assign to lhs of type %s." % type(lhs))
446
+
447
+ # Indexed types implement shape, but don't define it until later. This
448
+ # causes issues in assignment validation. For now, matrices are defined
449
+ # as anything with a shape that is not an Indexed
450
+ lhs_is_mat = hasattr(lhs, 'shape') and not isinstance(lhs, Indexed)
451
+ rhs_is_mat = hasattr(rhs, 'shape') and not isinstance(rhs, Indexed)
452
+
453
+ # If lhs and rhs have same structure, then this assignment is ok
454
+ if lhs_is_mat:
455
+ if not rhs_is_mat:
456
+ raise ValueError("Cannot assign a scalar to a matrix.")
457
+ elif lhs.shape != rhs.shape:
458
+ raise ValueError("Dimensions of lhs and rhs do not align.")
459
+ elif rhs_is_mat and not lhs_is_mat:
460
+ raise ValueError("Cannot assign a matrix to a scalar.")
461
+
462
+
463
+ class Assignment(AssignmentBase):
464
+ """
465
+ Represents variable assignment for code generation.
466
+
467
+ Parameters
468
+ ==========
469
+
470
+ lhs : Expr
471
+ SymPy object representing the lhs of the expression. These should be
472
+ singular objects, such as one would use in writing code. Notable types
473
+ include Symbol, MatrixSymbol, MatrixElement, and Indexed. Types that
474
+ subclass these types are also supported.
475
+
476
+ rhs : Expr
477
+ SymPy object representing the rhs of the expression. This can be any
478
+ type, provided its shape corresponds to that of the lhs. For example,
479
+ a Matrix type can be assigned to MatrixSymbol, but not to Symbol, as
480
+ the dimensions will not align.
481
+
482
+ Examples
483
+ ========
484
+
485
+ >>> from sympy import symbols, MatrixSymbol, Matrix
486
+ >>> from sympy.codegen.ast import Assignment
487
+ >>> x, y, z = symbols('x, y, z')
488
+ >>> Assignment(x, y)
489
+ Assignment(x, y)
490
+ >>> Assignment(x, 0)
491
+ Assignment(x, 0)
492
+ >>> A = MatrixSymbol('A', 1, 3)
493
+ >>> mat = Matrix([x, y, z]).T
494
+ >>> Assignment(A, mat)
495
+ Assignment(A, Matrix([[x, y, z]]))
496
+ >>> Assignment(A[0, 1], x)
497
+ Assignment(A[0, 1], x)
498
+ """
499
+
500
+ op = ':='
501
+
502
+
503
+ class AugmentedAssignment(AssignmentBase):
504
+ """
505
+ Base class for augmented assignments.
506
+
507
+ Attributes:
508
+ ===========
509
+
510
+ binop : str
511
+ Symbol for binary operation being applied in the assignment, such as "+",
512
+ "*", etc.
513
+ """
514
+ binop: str | None
515
+
516
+ @property
517
+ def op(self):
518
+ return self.binop + '='
519
+
520
+
521
+ class AddAugmentedAssignment(AugmentedAssignment):
522
+ binop = '+'
523
+
524
+
525
+ class SubAugmentedAssignment(AugmentedAssignment):
526
+ binop = '-'
527
+
528
+
529
+ class MulAugmentedAssignment(AugmentedAssignment):
530
+ binop = '*'
531
+
532
+
533
+ class DivAugmentedAssignment(AugmentedAssignment):
534
+ binop = '/'
535
+
536
+
537
+ class ModAugmentedAssignment(AugmentedAssignment):
538
+ binop = '%'
539
+
540
+
541
+ # Mapping from binary op strings to AugmentedAssignment subclasses
542
+ augassign_classes = {
543
+ cls.binop: cls for cls in [
544
+ AddAugmentedAssignment, SubAugmentedAssignment, MulAugmentedAssignment,
545
+ DivAugmentedAssignment, ModAugmentedAssignment
546
+ ]
547
+ }
548
+
549
+
550
+ def aug_assign(lhs, op, rhs):
551
+ """
552
+ Create 'lhs op= rhs'.
553
+
554
+ Explanation
555
+ ===========
556
+
557
+ Represents augmented variable assignment for code generation. This is a
558
+ convenience function. You can also use the AugmentedAssignment classes
559
+ directly, like AddAugmentedAssignment(x, y).
560
+
561
+ Parameters
562
+ ==========
563
+
564
+ lhs : Expr
565
+ SymPy object representing the lhs of the expression. These should be
566
+ singular objects, such as one would use in writing code. Notable types
567
+ include Symbol, MatrixSymbol, MatrixElement, and Indexed. Types that
568
+ subclass these types are also supported.
569
+
570
+ op : str
571
+ Operator (+, -, /, \\*, %).
572
+
573
+ rhs : Expr
574
+ SymPy object representing the rhs of the expression. This can be any
575
+ type, provided its shape corresponds to that of the lhs. For example,
576
+ a Matrix type can be assigned to MatrixSymbol, but not to Symbol, as
577
+ the dimensions will not align.
578
+
579
+ Examples
580
+ ========
581
+
582
+ >>> from sympy import symbols
583
+ >>> from sympy.codegen.ast import aug_assign
584
+ >>> x, y = symbols('x, y')
585
+ >>> aug_assign(x, '+', y)
586
+ AddAugmentedAssignment(x, y)
587
+ """
588
+ if op not in augassign_classes:
589
+ raise ValueError("Unrecognized operator %s" % op)
590
+ return augassign_classes[op](lhs, rhs)
591
+
592
+
593
+ class CodeBlock(CodegenAST):
594
+ """
595
+ Represents a block of code.
596
+
597
+ Explanation
598
+ ===========
599
+
600
+ For now only assignments are supported. This restriction will be lifted in
601
+ the future.
602
+
603
+ Useful attributes on this object are:
604
+
605
+ ``left_hand_sides``:
606
+ Tuple of left-hand sides of assignments, in order.
607
+ ``left_hand_sides``:
608
+ Tuple of right-hand sides of assignments, in order.
609
+ ``free_symbols``: Free symbols of the expressions in the right-hand sides
610
+ which do not appear in the left-hand side of an assignment.
611
+
612
+ Useful methods on this object are:
613
+
614
+ ``topological_sort``:
615
+ Class method. Return a CodeBlock with assignments
616
+ sorted so that variables are assigned before they
617
+ are used.
618
+ ``cse``:
619
+ Return a new CodeBlock with common subexpressions eliminated and
620
+ pulled out as assignments.
621
+
622
+ Examples
623
+ ========
624
+
625
+ >>> from sympy import symbols, ccode
626
+ >>> from sympy.codegen.ast import CodeBlock, Assignment
627
+ >>> x, y = symbols('x y')
628
+ >>> c = CodeBlock(Assignment(x, 1), Assignment(y, x + 1))
629
+ >>> print(ccode(c))
630
+ x = 1;
631
+ y = x + 1;
632
+
633
+ """
634
+ def __new__(cls, *args):
635
+ left_hand_sides = []
636
+ right_hand_sides = []
637
+ for i in args:
638
+ if isinstance(i, Assignment):
639
+ lhs, rhs = i.args
640
+ left_hand_sides.append(lhs)
641
+ right_hand_sides.append(rhs)
642
+
643
+ obj = CodegenAST.__new__(cls, *args)
644
+
645
+ obj.left_hand_sides = Tuple(*left_hand_sides)
646
+ obj.right_hand_sides = Tuple(*right_hand_sides)
647
+ return obj
648
+
649
+ def __iter__(self):
650
+ return iter(self.args)
651
+
652
+ def _sympyrepr(self, printer, *args, **kwargs):
653
+ il = printer._context.get('indent_level', 0)
654
+ joiner = ',\n' + ' '*il
655
+ joined = joiner.join(map(printer._print, self.args))
656
+ return ('{}(\n'.format(' '*(il-4) + self.__class__.__name__,) +
657
+ ' '*il + joined + '\n' + ' '*(il - 4) + ')')
658
+
659
+ _sympystr = _sympyrepr
660
+
661
+ @property
662
+ def free_symbols(self):
663
+ return super().free_symbols - set(self.left_hand_sides)
664
+
665
+ @classmethod
666
+ def topological_sort(cls, assignments):
667
+ """
668
+ Return a CodeBlock with topologically sorted assignments so that
669
+ variables are assigned before they are used.
670
+
671
+ Examples
672
+ ========
673
+
674
+ The existing order of assignments is preserved as much as possible.
675
+
676
+ This function assumes that variables are assigned to only once.
677
+
678
+ This is a class constructor so that the default constructor for
679
+ CodeBlock can error when variables are used before they are assigned.
680
+
681
+ >>> from sympy import symbols
682
+ >>> from sympy.codegen.ast import CodeBlock, Assignment
683
+ >>> x, y, z = symbols('x y z')
684
+
685
+ >>> assignments = [
686
+ ... Assignment(x, y + z),
687
+ ... Assignment(y, z + 1),
688
+ ... Assignment(z, 2),
689
+ ... ]
690
+ >>> CodeBlock.topological_sort(assignments)
691
+ CodeBlock(
692
+ Assignment(z, 2),
693
+ Assignment(y, z + 1),
694
+ Assignment(x, y + z)
695
+ )
696
+
697
+ """
698
+
699
+ if not all(isinstance(i, Assignment) for i in assignments):
700
+ # Will support more things later
701
+ raise NotImplementedError("CodeBlock.topological_sort only supports Assignments")
702
+
703
+ if any(isinstance(i, AugmentedAssignment) for i in assignments):
704
+ raise NotImplementedError("CodeBlock.topological_sort does not yet work with AugmentedAssignments")
705
+
706
+ # Create a graph where the nodes are assignments and there is a directed edge
707
+ # between nodes that use a variable and nodes that assign that
708
+ # variable, like
709
+
710
+ # [(x := 1, y := x + 1), (x := 1, z := y + z), (y := x + 1, z := y + z)]
711
+
712
+ # If we then topologically sort these nodes, they will be in
713
+ # assignment order, like
714
+
715
+ # x := 1
716
+ # y := x + 1
717
+ # z := y + z
718
+
719
+ # A = The nodes
720
+ #
721
+ # enumerate keeps nodes in the same order they are already in if
722
+ # possible. It will also allow us to handle duplicate assignments to
723
+ # the same variable when those are implemented.
724
+ A = list(enumerate(assignments))
725
+
726
+ # var_map = {variable: [nodes for which this variable is assigned to]}
727
+ # like {x: [(1, x := y + z), (4, x := 2 * w)], ...}
728
+ var_map = defaultdict(list)
729
+ for node in A:
730
+ i, a = node
731
+ var_map[a.lhs].append(node)
732
+
733
+ # E = Edges in the graph
734
+ E = []
735
+ for dst_node in A:
736
+ i, a = dst_node
737
+ for s in a.rhs.free_symbols:
738
+ for src_node in var_map[s]:
739
+ E.append((src_node, dst_node))
740
+
741
+ ordered_assignments = topological_sort([A, E])
742
+
743
+ # De-enumerate the result
744
+ return cls(*[a for i, a in ordered_assignments])
745
+
746
+ def cse(self, symbols=None, optimizations=None, postprocess=None,
747
+ order='canonical'):
748
+ """
749
+ Return a new code block with common subexpressions eliminated.
750
+
751
+ Explanation
752
+ ===========
753
+
754
+ See the docstring of :func:`sympy.simplify.cse_main.cse` for more
755
+ information.
756
+
757
+ Examples
758
+ ========
759
+
760
+ >>> from sympy import symbols, sin
761
+ >>> from sympy.codegen.ast import CodeBlock, Assignment
762
+ >>> x, y, z = symbols('x y z')
763
+
764
+ >>> c = CodeBlock(
765
+ ... Assignment(x, 1),
766
+ ... Assignment(y, sin(x) + 1),
767
+ ... Assignment(z, sin(x) - 1),
768
+ ... )
769
+ ...
770
+ >>> c.cse()
771
+ CodeBlock(
772
+ Assignment(x, 1),
773
+ Assignment(x0, sin(x)),
774
+ Assignment(y, x0 + 1),
775
+ Assignment(z, x0 - 1)
776
+ )
777
+
778
+ """
779
+ from sympy.simplify.cse_main import cse
780
+
781
+ # Check that the CodeBlock only contains assignments to unique variables
782
+ if not all(isinstance(i, Assignment) for i in self.args):
783
+ # Will support more things later
784
+ raise NotImplementedError("CodeBlock.cse only supports Assignments")
785
+
786
+ if any(isinstance(i, AugmentedAssignment) for i in self.args):
787
+ raise NotImplementedError("CodeBlock.cse does not yet work with AugmentedAssignments")
788
+
789
+ for i, lhs in enumerate(self.left_hand_sides):
790
+ if lhs in self.left_hand_sides[:i]:
791
+ raise NotImplementedError("Duplicate assignments to the same "
792
+ "variable are not yet supported (%s)" % lhs)
793
+
794
+ # Ensure new symbols for subexpressions do not conflict with existing
795
+ existing_symbols = self.atoms(Symbol)
796
+ if symbols is None:
797
+ symbols = numbered_symbols()
798
+ symbols = filter_symbols(symbols, existing_symbols)
799
+
800
+ replacements, reduced_exprs = cse(list(self.right_hand_sides),
801
+ symbols=symbols, optimizations=optimizations, postprocess=postprocess,
802
+ order=order)
803
+
804
+ new_block = [Assignment(var, expr) for var, expr in
805
+ zip(self.left_hand_sides, reduced_exprs)]
806
+ new_assignments = [Assignment(var, expr) for var, expr in replacements]
807
+ return self.topological_sort(new_assignments + new_block)
808
+
809
+
810
+ class For(Token):
811
+ """Represents a 'for-loop' in the code.
812
+
813
+ Expressions are of the form:
814
+ "for target in iter:
815
+ body..."
816
+
817
+ Parameters
818
+ ==========
819
+
820
+ target : symbol
821
+ iter : iterable
822
+ body : CodeBlock or iterable
823
+ ! When passed an iterable it is used to instantiate a CodeBlock.
824
+
825
+ Examples
826
+ ========
827
+
828
+ >>> from sympy import symbols, Range
829
+ >>> from sympy.codegen.ast import aug_assign, For
830
+ >>> x, i, j, k = symbols('x i j k')
831
+ >>> for_i = For(i, Range(10), [aug_assign(x, '+', i*j*k)])
832
+ >>> for_i # doctest: -NORMALIZE_WHITESPACE
833
+ For(i, iterable=Range(0, 10, 1), body=CodeBlock(
834
+ AddAugmentedAssignment(x, i*j*k)
835
+ ))
836
+ >>> for_ji = For(j, Range(7), [for_i])
837
+ >>> for_ji # doctest: -NORMALIZE_WHITESPACE
838
+ For(j, iterable=Range(0, 7, 1), body=CodeBlock(
839
+ For(i, iterable=Range(0, 10, 1), body=CodeBlock(
840
+ AddAugmentedAssignment(x, i*j*k)
841
+ ))
842
+ ))
843
+ >>> for_kji =For(k, Range(5), [for_ji])
844
+ >>> for_kji # doctest: -NORMALIZE_WHITESPACE
845
+ For(k, iterable=Range(0, 5, 1), body=CodeBlock(
846
+ For(j, iterable=Range(0, 7, 1), body=CodeBlock(
847
+ For(i, iterable=Range(0, 10, 1), body=CodeBlock(
848
+ AddAugmentedAssignment(x, i*j*k)
849
+ ))
850
+ ))
851
+ ))
852
+ """
853
+ __slots__ = _fields = ('target', 'iterable', 'body')
854
+ _construct_target = staticmethod(_sympify)
855
+
856
+ @classmethod
857
+ def _construct_body(cls, itr):
858
+ if isinstance(itr, CodeBlock):
859
+ return itr
860
+ else:
861
+ return CodeBlock(*itr)
862
+
863
+ @classmethod
864
+ def _construct_iterable(cls, itr):
865
+ if not iterable(itr):
866
+ raise TypeError("iterable must be an iterable")
867
+ if isinstance(itr, list): # _sympify errors on lists because they are mutable
868
+ itr = tuple(itr)
869
+ return _sympify(itr)
870
+
871
+
872
+ class String(Atom, Token):
873
+ """ SymPy object representing a string.
874
+
875
+ Atomic object which is not an expression (as opposed to Symbol).
876
+
877
+ Parameters
878
+ ==========
879
+
880
+ text : str
881
+
882
+ Examples
883
+ ========
884
+
885
+ >>> from sympy.codegen.ast import String
886
+ >>> f = String('foo')
887
+ >>> f
888
+ foo
889
+ >>> str(f)
890
+ 'foo'
891
+ >>> f.text
892
+ 'foo'
893
+ >>> print(repr(f))
894
+ String('foo')
895
+
896
+ """
897
+ __slots__ = _fields = ('text',)
898
+ not_in_args = ['text']
899
+ is_Atom = True
900
+
901
+ @classmethod
902
+ def _construct_text(cls, text):
903
+ if not isinstance(text, str):
904
+ raise TypeError("Argument text is not a string type.")
905
+ return text
906
+
907
+ def _sympystr(self, printer, *args, **kwargs):
908
+ return self.text
909
+
910
+ def kwargs(self, exclude = (), apply = None):
911
+ return {}
912
+
913
+ #to be removed when Atom is given a suitable func
914
+ @property
915
+ def func(self):
916
+ return lambda: self
917
+
918
+ def _latex(self, printer):
919
+ from sympy.printing.latex import latex_escape
920
+ return r'\texttt{{"{}"}}'.format(latex_escape(self.text))
921
+
922
+ class QuotedString(String):
923
+ """ Represents a string which should be printed with quotes. """
924
+
925
+ class Comment(String):
926
+ """ Represents a comment. """
927
+
928
+ class Node(Token):
929
+ """ Subclass of Token, carrying the attribute 'attrs' (Tuple)
930
+
931
+ Examples
932
+ ========
933
+
934
+ >>> from sympy.codegen.ast import Node, value_const, pointer_const
935
+ >>> n1 = Node([value_const])
936
+ >>> n1.attr_params('value_const') # get the parameters of attribute (by name)
937
+ ()
938
+ >>> from sympy.codegen.fnodes import dimension
939
+ >>> n2 = Node([value_const, dimension(5, 3)])
940
+ >>> n2.attr_params(value_const) # get the parameters of attribute (by Attribute instance)
941
+ ()
942
+ >>> n2.attr_params('dimension') # get the parameters of attribute (by name)
943
+ (5, 3)
944
+ >>> n2.attr_params(pointer_const) is None
945
+ True
946
+
947
+ """
948
+
949
+ __slots__: tuple[str, ...] = ('attrs',)
950
+ _fields = __slots__
951
+
952
+ defaults: dict[str, Any] = {'attrs': Tuple()}
953
+
954
+ _construct_attrs = staticmethod(_mk_Tuple)
955
+
956
+ def attr_params(self, looking_for):
957
+ """ Returns the parameters of the Attribute with name ``looking_for`` in self.attrs """
958
+ for attr in self.attrs:
959
+ if str(attr.name) == str(looking_for):
960
+ return attr.parameters
961
+
962
+
963
+ class Type(Token):
964
+ """ Represents a type.
965
+
966
+ Explanation
967
+ ===========
968
+
969
+ The naming is a super-set of NumPy naming. Type has a classmethod
970
+ ``from_expr`` which offer type deduction. It also has a method
971
+ ``cast_check`` which casts the argument to its type, possibly raising an
972
+ exception if rounding error is not within tolerances, or if the value is not
973
+ representable by the underlying data type (e.g. unsigned integers).
974
+
975
+ Parameters
976
+ ==========
977
+
978
+ name : str
979
+ Name of the type, e.g. ``object``, ``int16``, ``float16`` (where the latter two
980
+ would use the ``Type`` sub-classes ``IntType`` and ``FloatType`` respectively).
981
+ If a ``Type`` instance is given, the said instance is returned.
982
+
983
+ Examples
984
+ ========
985
+
986
+ >>> from sympy.codegen.ast import Type
987
+ >>> t = Type.from_expr(42)
988
+ >>> t
989
+ integer
990
+ >>> print(repr(t))
991
+ IntBaseType(String('integer'))
992
+ >>> from sympy.codegen.ast import uint8
993
+ >>> uint8.cast_check(-1) # doctest: +ELLIPSIS
994
+ Traceback (most recent call last):
995
+ ...
996
+ ValueError: Minimum value for data type bigger than new value.
997
+ >>> from sympy.codegen.ast import float32
998
+ >>> v6 = 0.123456
999
+ >>> float32.cast_check(v6)
1000
+ 0.123456
1001
+ >>> v10 = 12345.67894
1002
+ >>> float32.cast_check(v10) # doctest: +ELLIPSIS
1003
+ Traceback (most recent call last):
1004
+ ...
1005
+ ValueError: Casting gives a significantly different value.
1006
+ >>> boost_mp50 = Type('boost::multiprecision::cpp_dec_float_50')
1007
+ >>> from sympy import cxxcode
1008
+ >>> from sympy.codegen.ast import Declaration, Variable
1009
+ >>> cxxcode(Declaration(Variable('x', type=boost_mp50)))
1010
+ 'boost::multiprecision::cpp_dec_float_50 x'
1011
+
1012
+ References
1013
+ ==========
1014
+
1015
+ .. [1] https://numpy.org/doc/stable/user/basics.types.html
1016
+
1017
+ """
1018
+ __slots__: tuple[str, ...] = ('name',)
1019
+ _fields = __slots__
1020
+
1021
+ _construct_name = String
1022
+
1023
+ def _sympystr(self, printer, *args, **kwargs):
1024
+ return str(self.name)
1025
+
1026
+ @classmethod
1027
+ def from_expr(cls, expr):
1028
+ """ Deduces type from an expression or a ``Symbol``.
1029
+
1030
+ Parameters
1031
+ ==========
1032
+
1033
+ expr : number or SymPy object
1034
+ The type will be deduced from type or properties.
1035
+
1036
+ Examples
1037
+ ========
1038
+
1039
+ >>> from sympy.codegen.ast import Type, integer, complex_
1040
+ >>> Type.from_expr(2) == integer
1041
+ True
1042
+ >>> from sympy import Symbol
1043
+ >>> Type.from_expr(Symbol('z', complex=True)) == complex_
1044
+ True
1045
+ >>> Type.from_expr(sum) # doctest: +ELLIPSIS
1046
+ Traceback (most recent call last):
1047
+ ...
1048
+ ValueError: Could not deduce type from expr.
1049
+
1050
+ Raises
1051
+ ======
1052
+
1053
+ ValueError when type deduction fails.
1054
+
1055
+ """
1056
+ if isinstance(expr, (float, Float)):
1057
+ return real
1058
+ if isinstance(expr, (int, Integer)) or getattr(expr, 'is_integer', False):
1059
+ return integer
1060
+ if getattr(expr, 'is_real', False):
1061
+ return real
1062
+ if isinstance(expr, complex) or getattr(expr, 'is_complex', False):
1063
+ return complex_
1064
+ if isinstance(expr, bool) or getattr(expr, 'is_Relational', False):
1065
+ return bool_
1066
+ else:
1067
+ raise ValueError("Could not deduce type from expr.")
1068
+
1069
+ def _check(self, value):
1070
+ pass
1071
+
1072
+ def cast_check(self, value, rtol=None, atol=0, precision_targets=None):
1073
+ """ Casts a value to the data type of the instance.
1074
+
1075
+ Parameters
1076
+ ==========
1077
+
1078
+ value : number
1079
+ rtol : floating point number
1080
+ Relative tolerance. (will be deduced if not given).
1081
+ atol : floating point number
1082
+ Absolute tolerance (in addition to ``rtol``).
1083
+ type_aliases : dict
1084
+ Maps substitutions for Type, e.g. {integer: int64, real: float32}
1085
+
1086
+ Examples
1087
+ ========
1088
+
1089
+ >>> from sympy.codegen.ast import integer, float32, int8
1090
+ >>> integer.cast_check(3.0) == 3
1091
+ True
1092
+ >>> float32.cast_check(1e-40) # doctest: +ELLIPSIS
1093
+ Traceback (most recent call last):
1094
+ ...
1095
+ ValueError: Minimum value for data type bigger than new value.
1096
+ >>> int8.cast_check(256) # doctest: +ELLIPSIS
1097
+ Traceback (most recent call last):
1098
+ ...
1099
+ ValueError: Maximum value for data type smaller than new value.
1100
+ >>> v10 = 12345.67894
1101
+ >>> float32.cast_check(v10) # doctest: +ELLIPSIS
1102
+ Traceback (most recent call last):
1103
+ ...
1104
+ ValueError: Casting gives a significantly different value.
1105
+ >>> from sympy.codegen.ast import float64
1106
+ >>> float64.cast_check(v10)
1107
+ 12345.67894
1108
+ >>> from sympy import Float
1109
+ >>> v18 = Float('0.123456789012345646')
1110
+ >>> float64.cast_check(v18)
1111
+ Traceback (most recent call last):
1112
+ ...
1113
+ ValueError: Casting gives a significantly different value.
1114
+ >>> from sympy.codegen.ast import float80
1115
+ >>> float80.cast_check(v18)
1116
+ 0.123456789012345649
1117
+
1118
+ """
1119
+ val = sympify(value)
1120
+
1121
+ ten = Integer(10)
1122
+ exp10 = getattr(self, 'decimal_dig', None)
1123
+
1124
+ if rtol is None:
1125
+ rtol = 1e-15 if exp10 is None else 2.0*ten**(-exp10)
1126
+
1127
+ def tol(num):
1128
+ return atol + rtol*abs(num)
1129
+
1130
+ new_val = self.cast_nocheck(value)
1131
+ self._check(new_val)
1132
+
1133
+ delta = new_val - val
1134
+ if abs(delta) > tol(val): # rounding, e.g. int(3.5) != 3.5
1135
+ raise ValueError("Casting gives a significantly different value.")
1136
+
1137
+ return new_val
1138
+
1139
+ def _latex(self, printer):
1140
+ from sympy.printing.latex import latex_escape
1141
+ type_name = latex_escape(self.__class__.__name__)
1142
+ name = latex_escape(self.name.text)
1143
+ return r"\text{{{}}}\left(\texttt{{{}}}\right)".format(type_name, name)
1144
+
1145
+
1146
+ class IntBaseType(Type):
1147
+ """ Integer base type, contains no size information. """
1148
+ __slots__ = ()
1149
+ cast_nocheck = lambda self, i: Integer(int(i))
1150
+
1151
+
1152
+ class _SizedIntType(IntBaseType):
1153
+ __slots__ = ('nbits',)
1154
+ _fields = Type._fields + __slots__
1155
+
1156
+ _construct_nbits = Integer
1157
+
1158
+ def _check(self, value):
1159
+ if value < self.min:
1160
+ raise ValueError("Value is too small: %d < %d" % (value, self.min))
1161
+ if value > self.max:
1162
+ raise ValueError("Value is too big: %d > %d" % (value, self.max))
1163
+
1164
+
1165
+ class SignedIntType(_SizedIntType):
1166
+ """ Represents a signed integer type. """
1167
+ __slots__ = ()
1168
+ @property
1169
+ def min(self):
1170
+ return -2**(self.nbits-1)
1171
+
1172
+ @property
1173
+ def max(self):
1174
+ return 2**(self.nbits-1) - 1
1175
+
1176
+
1177
+ class UnsignedIntType(_SizedIntType):
1178
+ """ Represents an unsigned integer type. """
1179
+ __slots__ = ()
1180
+ @property
1181
+ def min(self):
1182
+ return 0
1183
+
1184
+ @property
1185
+ def max(self):
1186
+ return 2**self.nbits - 1
1187
+
1188
+ two = Integer(2)
1189
+
1190
+ class FloatBaseType(Type):
1191
+ """ Represents a floating point number type. """
1192
+ __slots__ = ()
1193
+ cast_nocheck = Float
1194
+
1195
+ class FloatType(FloatBaseType):
1196
+ """ Represents a floating point type with fixed bit width.
1197
+
1198
+ Base 2 & one sign bit is assumed.
1199
+
1200
+ Parameters
1201
+ ==========
1202
+
1203
+ name : str
1204
+ Name of the type.
1205
+ nbits : integer
1206
+ Number of bits used (storage).
1207
+ nmant : integer
1208
+ Number of bits used to represent the mantissa.
1209
+ nexp : integer
1210
+ Number of bits used to represent the mantissa.
1211
+
1212
+ Examples
1213
+ ========
1214
+
1215
+ >>> from sympy import S
1216
+ >>> from sympy.codegen.ast import FloatType
1217
+ >>> half_precision = FloatType('f16', nbits=16, nmant=10, nexp=5)
1218
+ >>> half_precision.max
1219
+ 65504
1220
+ >>> half_precision.tiny == S(2)**-14
1221
+ True
1222
+ >>> half_precision.eps == S(2)**-10
1223
+ True
1224
+ >>> half_precision.dig == 3
1225
+ True
1226
+ >>> half_precision.decimal_dig == 5
1227
+ True
1228
+ >>> half_precision.cast_check(1.0)
1229
+ 1.0
1230
+ >>> half_precision.cast_check(1e5) # doctest: +ELLIPSIS
1231
+ Traceback (most recent call last):
1232
+ ...
1233
+ ValueError: Maximum value for data type smaller than new value.
1234
+ """
1235
+
1236
+ __slots__ = ('nbits', 'nmant', 'nexp',)
1237
+ _fields = Type._fields + __slots__
1238
+
1239
+ _construct_nbits = _construct_nmant = _construct_nexp = Integer
1240
+
1241
+
1242
+ @property
1243
+ def max_exponent(self):
1244
+ """ The largest positive number n, such that 2**(n - 1) is a representable finite value. """
1245
+ # cf. C++'s ``std::numeric_limits::max_exponent``
1246
+ return two**(self.nexp - 1)
1247
+
1248
+ @property
1249
+ def min_exponent(self):
1250
+ """ The lowest negative number n, such that 2**(n - 1) is a valid normalized number. """
1251
+ # cf. C++'s ``std::numeric_limits::min_exponent``
1252
+ return 3 - self.max_exponent
1253
+
1254
+ @property
1255
+ def max(self):
1256
+ """ Maximum value representable. """
1257
+ return (1 - two**-(self.nmant+1))*two**self.max_exponent
1258
+
1259
+ @property
1260
+ def tiny(self):
1261
+ """ The minimum positive normalized value. """
1262
+ # See C macros: FLT_MIN, DBL_MIN, LDBL_MIN
1263
+ # or C++'s ``std::numeric_limits::min``
1264
+ # or numpy.finfo(dtype).tiny
1265
+ return two**(self.min_exponent - 1)
1266
+
1267
+
1268
+ @property
1269
+ def eps(self):
1270
+ """ Difference between 1.0 and the next representable value. """
1271
+ return two**(-self.nmant)
1272
+
1273
+ @property
1274
+ def dig(self):
1275
+ """ Number of decimal digits that are guaranteed to be preserved in text.
1276
+
1277
+ When converting text -> float -> text, you are guaranteed that at least ``dig``
1278
+ number of digits are preserved with respect to rounding or overflow.
1279
+ """
1280
+ from sympy.functions import floor, log
1281
+ return floor(self.nmant * log(2)/log(10))
1282
+
1283
+ @property
1284
+ def decimal_dig(self):
1285
+ """ Number of digits needed to store & load without loss.
1286
+
1287
+ Explanation
1288
+ ===========
1289
+
1290
+ Number of decimal digits needed to guarantee that two consecutive conversions
1291
+ (float -> text -> float) to be idempotent. This is useful when one do not want
1292
+ to loose precision due to rounding errors when storing a floating point value
1293
+ as text.
1294
+ """
1295
+ from sympy.functions import ceiling, log
1296
+ return ceiling((self.nmant + 1) * log(2)/log(10) + 1)
1297
+
1298
+ def cast_nocheck(self, value):
1299
+ """ Casts without checking if out of bounds or subnormal. """
1300
+ if value == oo: # float(oo) or oo
1301
+ return float(oo)
1302
+ elif value == -oo: # float(-oo) or -oo
1303
+ return float(-oo)
1304
+ return Float(str(sympify(value).evalf(self.decimal_dig)), self.decimal_dig)
1305
+
1306
+ def _check(self, value):
1307
+ if value < -self.max:
1308
+ raise ValueError("Value is too small: %d < %d" % (value, -self.max))
1309
+ if value > self.max:
1310
+ raise ValueError("Value is too big: %d > %d" % (value, self.max))
1311
+ if abs(value) < self.tiny:
1312
+ raise ValueError("Smallest (absolute) value for data type bigger than new value.")
1313
+
1314
+ class ComplexBaseType(FloatBaseType):
1315
+
1316
+ __slots__ = ()
1317
+
1318
+ def cast_nocheck(self, value):
1319
+ """ Casts without checking if out of bounds or subnormal. """
1320
+ from sympy.functions import re, im
1321
+ return (
1322
+ super().cast_nocheck(re(value)) +
1323
+ super().cast_nocheck(im(value))*1j
1324
+ )
1325
+
1326
+ def _check(self, value):
1327
+ from sympy.functions import re, im
1328
+ super()._check(re(value))
1329
+ super()._check(im(value))
1330
+
1331
+
1332
+ class ComplexType(ComplexBaseType, FloatType):
1333
+ """ Represents a complex floating point number. """
1334
+ __slots__ = ()
1335
+
1336
+
1337
+ # NumPy types:
1338
+ intc = IntBaseType('intc')
1339
+ intp = IntBaseType('intp')
1340
+ int8 = SignedIntType('int8', 8)
1341
+ int16 = SignedIntType('int16', 16)
1342
+ int32 = SignedIntType('int32', 32)
1343
+ int64 = SignedIntType('int64', 64)
1344
+ uint8 = UnsignedIntType('uint8', 8)
1345
+ uint16 = UnsignedIntType('uint16', 16)
1346
+ uint32 = UnsignedIntType('uint32', 32)
1347
+ uint64 = UnsignedIntType('uint64', 64)
1348
+ float16 = FloatType('float16', 16, nexp=5, nmant=10) # IEEE 754 binary16, Half precision
1349
+ float32 = FloatType('float32', 32, nexp=8, nmant=23) # IEEE 754 binary32, Single precision
1350
+ float64 = FloatType('float64', 64, nexp=11, nmant=52) # IEEE 754 binary64, Double precision
1351
+ float80 = FloatType('float80', 80, nexp=15, nmant=63) # x86 extended precision (1 integer part bit), "long double"
1352
+ float128 = FloatType('float128', 128, nexp=15, nmant=112) # IEEE 754 binary128, Quadruple precision
1353
+ float256 = FloatType('float256', 256, nexp=19, nmant=236) # IEEE 754 binary256, Octuple precision
1354
+
1355
+ complex64 = ComplexType('complex64', nbits=64, **float32.kwargs(exclude=('name', 'nbits')))
1356
+ complex128 = ComplexType('complex128', nbits=128, **float64.kwargs(exclude=('name', 'nbits')))
1357
+
1358
+ # Generic types (precision may be chosen by code printers):
1359
+ untyped = Type('untyped')
1360
+ real = FloatBaseType('real')
1361
+ integer = IntBaseType('integer')
1362
+ complex_ = ComplexBaseType('complex')
1363
+ bool_ = Type('bool')
1364
+
1365
+
1366
+ class Attribute(Token):
1367
+ """ Attribute (possibly parametrized)
1368
+
1369
+ For use with :class:`sympy.codegen.ast.Node` (which takes instances of
1370
+ ``Attribute`` as ``attrs``).
1371
+
1372
+ Parameters
1373
+ ==========
1374
+
1375
+ name : str
1376
+ parameters : Tuple
1377
+
1378
+ Examples
1379
+ ========
1380
+
1381
+ >>> from sympy.codegen.ast import Attribute
1382
+ >>> volatile = Attribute('volatile')
1383
+ >>> volatile
1384
+ volatile
1385
+ >>> print(repr(volatile))
1386
+ Attribute(String('volatile'))
1387
+ >>> a = Attribute('foo', [1, 2, 3])
1388
+ >>> a
1389
+ foo(1, 2, 3)
1390
+ >>> a.parameters == (1, 2, 3)
1391
+ True
1392
+ """
1393
+ __slots__ = _fields = ('name', 'parameters')
1394
+ defaults = {'parameters': Tuple()}
1395
+
1396
+ _construct_name = String
1397
+ _construct_parameters = staticmethod(_mk_Tuple)
1398
+
1399
+ def _sympystr(self, printer, *args, **kwargs):
1400
+ result = str(self.name)
1401
+ if self.parameters:
1402
+ result += '(%s)' % ', '.join((printer._print(
1403
+ arg, *args, **kwargs) for arg in self.parameters))
1404
+ return result
1405
+
1406
+ value_const = Attribute('value_const')
1407
+ pointer_const = Attribute('pointer_const')
1408
+
1409
+
1410
+ class Variable(Node):
1411
+ """ Represents a variable.
1412
+
1413
+ Parameters
1414
+ ==========
1415
+
1416
+ symbol : Symbol
1417
+ type : Type (optional)
1418
+ Type of the variable.
1419
+ attrs : iterable of Attribute instances
1420
+ Will be stored as a Tuple.
1421
+
1422
+ Examples
1423
+ ========
1424
+
1425
+ >>> from sympy import Symbol
1426
+ >>> from sympy.codegen.ast import Variable, float32, integer
1427
+ >>> x = Symbol('x')
1428
+ >>> v = Variable(x, type=float32)
1429
+ >>> v.attrs
1430
+ ()
1431
+ >>> v == Variable('x')
1432
+ False
1433
+ >>> v == Variable('x', type=float32)
1434
+ True
1435
+ >>> v
1436
+ Variable(x, type=float32)
1437
+
1438
+ One may also construct a ``Variable`` instance with the type deduced from
1439
+ assumptions about the symbol using the ``deduced`` classmethod:
1440
+
1441
+ >>> i = Symbol('i', integer=True)
1442
+ >>> v = Variable.deduced(i)
1443
+ >>> v.type == integer
1444
+ True
1445
+ >>> v == Variable('i')
1446
+ False
1447
+ >>> from sympy.codegen.ast import value_const
1448
+ >>> value_const in v.attrs
1449
+ False
1450
+ >>> w = Variable('w', attrs=[value_const])
1451
+ >>> w
1452
+ Variable(w, attrs=(value_const,))
1453
+ >>> value_const in w.attrs
1454
+ True
1455
+ >>> w.as_Declaration(value=42)
1456
+ Declaration(Variable(w, value=42, attrs=(value_const,)))
1457
+
1458
+ """
1459
+
1460
+ __slots__ = ('symbol', 'type', 'value')
1461
+ _fields = __slots__ + Node._fields
1462
+
1463
+ defaults = Node.defaults.copy()
1464
+ defaults.update({'type': untyped, 'value': none})
1465
+
1466
+ _construct_symbol = staticmethod(sympify)
1467
+ _construct_value = staticmethod(sympify)
1468
+
1469
+ @classmethod
1470
+ def deduced(cls, symbol, value=None, attrs=Tuple(), cast_check=True):
1471
+ """ Alt. constructor with type deduction from ``Type.from_expr``.
1472
+
1473
+ Deduces type primarily from ``symbol``, secondarily from ``value``.
1474
+
1475
+ Parameters
1476
+ ==========
1477
+
1478
+ symbol : Symbol
1479
+ value : expr
1480
+ (optional) value of the variable.
1481
+ attrs : iterable of Attribute instances
1482
+ cast_check : bool
1483
+ Whether to apply ``Type.cast_check`` on ``value``.
1484
+
1485
+ Examples
1486
+ ========
1487
+
1488
+ >>> from sympy import Symbol
1489
+ >>> from sympy.codegen.ast import Variable, complex_
1490
+ >>> n = Symbol('n', integer=True)
1491
+ >>> str(Variable.deduced(n).type)
1492
+ 'integer'
1493
+ >>> x = Symbol('x', real=True)
1494
+ >>> v = Variable.deduced(x)
1495
+ >>> v.type
1496
+ real
1497
+ >>> z = Symbol('z', complex=True)
1498
+ >>> Variable.deduced(z).type == complex_
1499
+ True
1500
+
1501
+ """
1502
+ if isinstance(symbol, Variable):
1503
+ return symbol
1504
+
1505
+ try:
1506
+ type_ = Type.from_expr(symbol)
1507
+ except ValueError:
1508
+ type_ = Type.from_expr(value)
1509
+
1510
+ if value is not None and cast_check:
1511
+ value = type_.cast_check(value)
1512
+ return cls(symbol, type=type_, value=value, attrs=attrs)
1513
+
1514
+ def as_Declaration(self, **kwargs):
1515
+ """ Convenience method for creating a Declaration instance.
1516
+
1517
+ Explanation
1518
+ ===========
1519
+
1520
+ If the variable of the Declaration need to wrap a modified
1521
+ variable keyword arguments may be passed (overriding e.g.
1522
+ the ``value`` of the Variable instance).
1523
+
1524
+ Examples
1525
+ ========
1526
+
1527
+ >>> from sympy.codegen.ast import Variable, NoneToken
1528
+ >>> x = Variable('x')
1529
+ >>> decl1 = x.as_Declaration()
1530
+ >>> # value is special NoneToken() which must be tested with == operator
1531
+ >>> decl1.variable.value is None # won't work
1532
+ False
1533
+ >>> decl1.variable.value == None # not PEP-8 compliant
1534
+ True
1535
+ >>> decl1.variable.value == NoneToken() # OK
1536
+ True
1537
+ >>> decl2 = x.as_Declaration(value=42.0)
1538
+ >>> decl2.variable.value == 42.0
1539
+ True
1540
+
1541
+ """
1542
+ kw = self.kwargs()
1543
+ kw.update(kwargs)
1544
+ return Declaration(self.func(**kw))
1545
+
1546
+ def _relation(self, rhs, op):
1547
+ try:
1548
+ rhs = _sympify(rhs)
1549
+ except SympifyError:
1550
+ raise TypeError("Invalid comparison %s < %s" % (self, rhs))
1551
+ return op(self, rhs, evaluate=False)
1552
+
1553
+ __lt__ = lambda self, other: self._relation(other, Lt)
1554
+ __le__ = lambda self, other: self._relation(other, Le)
1555
+ __ge__ = lambda self, other: self._relation(other, Ge)
1556
+ __gt__ = lambda self, other: self._relation(other, Gt)
1557
+
1558
+ class Pointer(Variable):
1559
+ """ Represents a pointer. See ``Variable``.
1560
+
1561
+ Examples
1562
+ ========
1563
+
1564
+ Can create instances of ``Element``:
1565
+
1566
+ >>> from sympy import Symbol
1567
+ >>> from sympy.codegen.ast import Pointer
1568
+ >>> i = Symbol('i', integer=True)
1569
+ >>> p = Pointer('x')
1570
+ >>> p[i+1]
1571
+ Element(x, indices=(i + 1,))
1572
+
1573
+ """
1574
+ __slots__ = ()
1575
+
1576
+ def __getitem__(self, key):
1577
+ try:
1578
+ return Element(self.symbol, key)
1579
+ except TypeError:
1580
+ return Element(self.symbol, (key,))
1581
+
1582
+
1583
+ class Element(Token):
1584
+ """ Element in (a possibly N-dimensional) array.
1585
+
1586
+ Examples
1587
+ ========
1588
+
1589
+ >>> from sympy.codegen.ast import Element
1590
+ >>> elem = Element('x', 'ijk')
1591
+ >>> elem.symbol.name == 'x'
1592
+ True
1593
+ >>> elem.indices
1594
+ (i, j, k)
1595
+ >>> from sympy import ccode
1596
+ >>> ccode(elem)
1597
+ 'x[i][j][k]'
1598
+ >>> ccode(Element('x', 'ijk', strides='lmn', offset='o'))
1599
+ 'x[i*l + j*m + k*n + o]'
1600
+
1601
+ """
1602
+ __slots__ = _fields = ('symbol', 'indices', 'strides', 'offset')
1603
+ defaults = {'strides': none, 'offset': none}
1604
+ _construct_symbol = staticmethod(sympify)
1605
+ _construct_indices = staticmethod(lambda arg: Tuple(*arg))
1606
+ _construct_strides = staticmethod(lambda arg: Tuple(*arg))
1607
+ _construct_offset = staticmethod(sympify)
1608
+
1609
+
1610
+ class Declaration(Token):
1611
+ """ Represents a variable declaration
1612
+
1613
+ Parameters
1614
+ ==========
1615
+
1616
+ variable : Variable
1617
+
1618
+ Examples
1619
+ ========
1620
+
1621
+ >>> from sympy.codegen.ast import Declaration, NoneToken, untyped
1622
+ >>> z = Declaration('z')
1623
+ >>> z.variable.type == untyped
1624
+ True
1625
+ >>> # value is special NoneToken() which must be tested with == operator
1626
+ >>> z.variable.value is None # won't work
1627
+ False
1628
+ >>> z.variable.value == None # not PEP-8 compliant
1629
+ True
1630
+ >>> z.variable.value == NoneToken() # OK
1631
+ True
1632
+ """
1633
+ __slots__ = _fields = ('variable',)
1634
+ _construct_variable = Variable
1635
+
1636
+
1637
+ class While(Token):
1638
+ """ Represents a 'for-loop' in the code.
1639
+
1640
+ Expressions are of the form:
1641
+ "while condition:
1642
+ body..."
1643
+
1644
+ Parameters
1645
+ ==========
1646
+
1647
+ condition : expression convertible to Boolean
1648
+ body : CodeBlock or iterable
1649
+ When passed an iterable it is used to instantiate a CodeBlock.
1650
+
1651
+ Examples
1652
+ ========
1653
+
1654
+ >>> from sympy import symbols, Gt, Abs
1655
+ >>> from sympy.codegen import aug_assign, Assignment, While
1656
+ >>> x, dx = symbols('x dx')
1657
+ >>> expr = 1 - x**2
1658
+ >>> whl = While(Gt(Abs(dx), 1e-9), [
1659
+ ... Assignment(dx, -expr/expr.diff(x)),
1660
+ ... aug_assign(x, '+', dx)
1661
+ ... ])
1662
+
1663
+ """
1664
+ __slots__ = _fields = ('condition', 'body')
1665
+ _construct_condition = staticmethod(lambda cond: _sympify(cond))
1666
+
1667
+ @classmethod
1668
+ def _construct_body(cls, itr):
1669
+ if isinstance(itr, CodeBlock):
1670
+ return itr
1671
+ else:
1672
+ return CodeBlock(*itr)
1673
+
1674
+
1675
+ class Scope(Token):
1676
+ """ Represents a scope in the code.
1677
+
1678
+ Parameters
1679
+ ==========
1680
+
1681
+ body : CodeBlock or iterable
1682
+ When passed an iterable it is used to instantiate a CodeBlock.
1683
+
1684
+ """
1685
+ __slots__ = _fields = ('body',)
1686
+
1687
+ @classmethod
1688
+ def _construct_body(cls, itr):
1689
+ if isinstance(itr, CodeBlock):
1690
+ return itr
1691
+ else:
1692
+ return CodeBlock(*itr)
1693
+
1694
+
1695
+ class Stream(Token):
1696
+ """ Represents a stream.
1697
+
1698
+ There are two predefined Stream instances ``stdout`` & ``stderr``.
1699
+
1700
+ Parameters
1701
+ ==========
1702
+
1703
+ name : str
1704
+
1705
+ Examples
1706
+ ========
1707
+
1708
+ >>> from sympy import pycode, Symbol
1709
+ >>> from sympy.codegen.ast import Print, stderr, QuotedString
1710
+ >>> print(pycode(Print(['x'], file=stderr)))
1711
+ print(x, file=sys.stderr)
1712
+ >>> x = Symbol('x')
1713
+ >>> print(pycode(Print([QuotedString('x')], file=stderr))) # print literally "x"
1714
+ print("x", file=sys.stderr)
1715
+
1716
+ """
1717
+ __slots__ = _fields = ('name',)
1718
+ _construct_name = String
1719
+
1720
+ stdout = Stream('stdout')
1721
+ stderr = Stream('stderr')
1722
+
1723
+
1724
+ class Print(Token):
1725
+ r""" Represents print command in the code.
1726
+
1727
+ Parameters
1728
+ ==========
1729
+
1730
+ formatstring : str
1731
+ *args : Basic instances (or convertible to such through sympify)
1732
+
1733
+ Examples
1734
+ ========
1735
+
1736
+ >>> from sympy.codegen.ast import Print
1737
+ >>> from sympy import pycode
1738
+ >>> print(pycode(Print('x y'.split(), "coordinate: %12.5g %12.5g\\n")))
1739
+ print("coordinate: %12.5g %12.5g\n" % (x, y), end="")
1740
+
1741
+ """
1742
+
1743
+ __slots__ = _fields = ('print_args', 'format_string', 'file')
1744
+ defaults = {'format_string': none, 'file': none}
1745
+
1746
+ _construct_print_args = staticmethod(_mk_Tuple)
1747
+ _construct_format_string = QuotedString
1748
+ _construct_file = Stream
1749
+
1750
+
1751
+ class FunctionPrototype(Node):
1752
+ """ Represents a function prototype
1753
+
1754
+ Allows the user to generate forward declaration in e.g. C/C++.
1755
+
1756
+ Parameters
1757
+ ==========
1758
+
1759
+ return_type : Type
1760
+ name : str
1761
+ parameters: iterable of Variable instances
1762
+ attrs : iterable of Attribute instances
1763
+
1764
+ Examples
1765
+ ========
1766
+
1767
+ >>> from sympy import ccode, symbols
1768
+ >>> from sympy.codegen.ast import real, FunctionPrototype
1769
+ >>> x, y = symbols('x y', real=True)
1770
+ >>> fp = FunctionPrototype(real, 'foo', [x, y])
1771
+ >>> ccode(fp)
1772
+ 'double foo(double x, double y)'
1773
+
1774
+ """
1775
+
1776
+ __slots__ = ('return_type', 'name', 'parameters')
1777
+ _fields: tuple[str, ...] = __slots__ + Node._fields
1778
+
1779
+ _construct_return_type = Type
1780
+ _construct_name = String
1781
+
1782
+ @staticmethod
1783
+ def _construct_parameters(args):
1784
+ def _var(arg):
1785
+ if isinstance(arg, Declaration):
1786
+ return arg.variable
1787
+ elif isinstance(arg, Variable):
1788
+ return arg
1789
+ else:
1790
+ return Variable.deduced(arg)
1791
+ return Tuple(*map(_var, args))
1792
+
1793
+ @classmethod
1794
+ def from_FunctionDefinition(cls, func_def):
1795
+ if not isinstance(func_def, FunctionDefinition):
1796
+ raise TypeError("func_def is not an instance of FunctionDefinition")
1797
+ return cls(**func_def.kwargs(exclude=('body',)))
1798
+
1799
+
1800
+ class FunctionDefinition(FunctionPrototype):
1801
+ """ Represents a function definition in the code.
1802
+
1803
+ Parameters
1804
+ ==========
1805
+
1806
+ return_type : Type
1807
+ name : str
1808
+ parameters: iterable of Variable instances
1809
+ body : CodeBlock or iterable
1810
+ attrs : iterable of Attribute instances
1811
+
1812
+ Examples
1813
+ ========
1814
+
1815
+ >>> from sympy import ccode, symbols
1816
+ >>> from sympy.codegen.ast import real, FunctionPrototype
1817
+ >>> x, y = symbols('x y', real=True)
1818
+ >>> fp = FunctionPrototype(real, 'foo', [x, y])
1819
+ >>> ccode(fp)
1820
+ 'double foo(double x, double y)'
1821
+ >>> from sympy.codegen.ast import FunctionDefinition, Return
1822
+ >>> body = [Return(x*y)]
1823
+ >>> fd = FunctionDefinition.from_FunctionPrototype(fp, body)
1824
+ >>> print(ccode(fd))
1825
+ double foo(double x, double y){
1826
+ return x*y;
1827
+ }
1828
+ """
1829
+
1830
+ __slots__ = ('body', )
1831
+ _fields = FunctionPrototype._fields[:-1] + __slots__ + Node._fields
1832
+
1833
+ @classmethod
1834
+ def _construct_body(cls, itr):
1835
+ if isinstance(itr, CodeBlock):
1836
+ return itr
1837
+ else:
1838
+ return CodeBlock(*itr)
1839
+
1840
+ @classmethod
1841
+ def from_FunctionPrototype(cls, func_proto, body):
1842
+ if not isinstance(func_proto, FunctionPrototype):
1843
+ raise TypeError("func_proto is not an instance of FunctionPrototype")
1844
+ return cls(body=body, **func_proto.kwargs())
1845
+
1846
+
1847
+ class Return(Token):
1848
+ """ Represents a return command in the code.
1849
+
1850
+ Parameters
1851
+ ==========
1852
+
1853
+ return : Basic
1854
+
1855
+ Examples
1856
+ ========
1857
+
1858
+ >>> from sympy.codegen.ast import Return
1859
+ >>> from sympy.printing.pycode import pycode
1860
+ >>> from sympy import Symbol
1861
+ >>> x = Symbol('x')
1862
+ >>> print(pycode(Return(x)))
1863
+ return x
1864
+
1865
+ """
1866
+ __slots__ = _fields = ('return',)
1867
+ _construct_return=staticmethod(_sympify)
1868
+
1869
+
1870
+ class FunctionCall(Token, Expr):
1871
+ """ Represents a call to a function in the code.
1872
+
1873
+ Parameters
1874
+ ==========
1875
+
1876
+ name : str
1877
+ function_args : Tuple
1878
+
1879
+ Examples
1880
+ ========
1881
+
1882
+ >>> from sympy.codegen.ast import FunctionCall
1883
+ >>> from sympy import pycode
1884
+ >>> fcall = FunctionCall('foo', 'bar baz'.split())
1885
+ >>> print(pycode(fcall))
1886
+ foo(bar, baz)
1887
+
1888
+ """
1889
+ __slots__ = _fields = ('name', 'function_args')
1890
+
1891
+ _construct_name = String
1892
+ _construct_function_args = staticmethod(lambda args: Tuple(*args))
1893
+
1894
+
1895
+ class Raise(Token):
1896
+ """ Prints as 'raise ...' in Python, 'throw ...' in C++"""
1897
+ __slots__ = _fields = ('exception',)
1898
+
1899
+
1900
+ class RuntimeError_(Token):
1901
+ """ Represents 'std::runtime_error' in C++ and 'RuntimeError' in Python.
1902
+
1903
+ Note that the latter is uncommon, and you might want to use e.g. ValueError.
1904
+ """
1905
+ __slots__ = _fields = ('message',)
1906
+ _construct_message = String
.venv/lib/python3.13/site-packages/sympy/codegen/cfunctions.py ADDED
@@ -0,0 +1,558 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ This module contains SymPy functions mathcin corresponding to special math functions in the
3
+ C standard library (since C99, also available in C++11).
4
+
5
+ The functions defined in this module allows the user to express functions such as ``expm1``
6
+ as a SymPy function for symbolic manipulation.
7
+
8
+ """
9
+ from sympy.core.function import ArgumentIndexError, Function
10
+ from sympy.core.numbers import Rational
11
+ from sympy.core.power import Pow
12
+ from sympy.core.singleton import S
13
+ from sympy.functions.elementary.exponential import exp, log
14
+ from sympy.functions.elementary.miscellaneous import sqrt
15
+ from sympy.logic.boolalg import BooleanFunction, true, false
16
+
17
+ def _expm1(x):
18
+ return exp(x) - S.One
19
+
20
+
21
+ class expm1(Function):
22
+ """
23
+ Represents the exponential function minus one.
24
+
25
+ Explanation
26
+ ===========
27
+
28
+ The benefit of using ``expm1(x)`` over ``exp(x) - 1``
29
+ is that the latter is prone to cancellation under finite precision
30
+ arithmetic when x is close to zero.
31
+
32
+ Examples
33
+ ========
34
+
35
+ >>> from sympy.abc import x
36
+ >>> from sympy.codegen.cfunctions import expm1
37
+ >>> '%.0e' % expm1(1e-99).evalf()
38
+ '1e-99'
39
+ >>> from math import exp
40
+ >>> exp(1e-99) - 1
41
+ 0.0
42
+ >>> expm1(x).diff(x)
43
+ exp(x)
44
+
45
+ See Also
46
+ ========
47
+
48
+ log1p
49
+ """
50
+ nargs = 1
51
+
52
+ def fdiff(self, argindex=1):
53
+ """
54
+ Returns the first derivative of this function.
55
+ """
56
+ if argindex == 1:
57
+ return exp(*self.args)
58
+ else:
59
+ raise ArgumentIndexError(self, argindex)
60
+
61
+ def _eval_expand_func(self, **hints):
62
+ return _expm1(*self.args)
63
+
64
+ def _eval_rewrite_as_exp(self, arg, **kwargs):
65
+ return exp(arg) - S.One
66
+
67
+ _eval_rewrite_as_tractable = _eval_rewrite_as_exp
68
+
69
+ @classmethod
70
+ def eval(cls, arg):
71
+ exp_arg = exp.eval(arg)
72
+ if exp_arg is not None:
73
+ return exp_arg - S.One
74
+
75
+ def _eval_is_real(self):
76
+ return self.args[0].is_real
77
+
78
+ def _eval_is_finite(self):
79
+ return self.args[0].is_finite
80
+
81
+
82
+ def _log1p(x):
83
+ return log(x + S.One)
84
+
85
+
86
+ class log1p(Function):
87
+ """
88
+ Represents the natural logarithm of a number plus one.
89
+
90
+ Explanation
91
+ ===========
92
+
93
+ The benefit of using ``log1p(x)`` over ``log(x + 1)``
94
+ is that the latter is prone to cancellation under finite precision
95
+ arithmetic when x is close to zero.
96
+
97
+ Examples
98
+ ========
99
+
100
+ >>> from sympy.abc import x
101
+ >>> from sympy.codegen.cfunctions import log1p
102
+ >>> from sympy import expand_log
103
+ >>> '%.0e' % expand_log(log1p(1e-99)).evalf()
104
+ '1e-99'
105
+ >>> from math import log
106
+ >>> log(1 + 1e-99)
107
+ 0.0
108
+ >>> log1p(x).diff(x)
109
+ 1/(x + 1)
110
+
111
+ See Also
112
+ ========
113
+
114
+ expm1
115
+ """
116
+ nargs = 1
117
+
118
+
119
+ def fdiff(self, argindex=1):
120
+ """
121
+ Returns the first derivative of this function.
122
+ """
123
+ if argindex == 1:
124
+ return S.One/(self.args[0] + S.One)
125
+ else:
126
+ raise ArgumentIndexError(self, argindex)
127
+
128
+
129
+ def _eval_expand_func(self, **hints):
130
+ return _log1p(*self.args)
131
+
132
+ def _eval_rewrite_as_log(self, arg, **kwargs):
133
+ return _log1p(arg)
134
+
135
+ _eval_rewrite_as_tractable = _eval_rewrite_as_log
136
+
137
+ @classmethod
138
+ def eval(cls, arg):
139
+ if arg.is_Rational:
140
+ return log(arg + S.One)
141
+ elif not arg.is_Float: # not safe to add 1 to Float
142
+ return log.eval(arg + S.One)
143
+ elif arg.is_number:
144
+ return log(Rational(arg) + S.One)
145
+
146
+ def _eval_is_real(self):
147
+ return (self.args[0] + S.One).is_nonnegative
148
+
149
+ def _eval_is_finite(self):
150
+ if (self.args[0] + S.One).is_zero:
151
+ return False
152
+ return self.args[0].is_finite
153
+
154
+ def _eval_is_positive(self):
155
+ return self.args[0].is_positive
156
+
157
+ def _eval_is_zero(self):
158
+ return self.args[0].is_zero
159
+
160
+ def _eval_is_nonnegative(self):
161
+ return self.args[0].is_nonnegative
162
+
163
+ _Two = S(2)
164
+
165
+ def _exp2(x):
166
+ return Pow(_Two, x)
167
+
168
+ class exp2(Function):
169
+ """
170
+ Represents the exponential function with base two.
171
+
172
+ Explanation
173
+ ===========
174
+
175
+ The benefit of using ``exp2(x)`` over ``2**x``
176
+ is that the latter is not as efficient under finite precision
177
+ arithmetic.
178
+
179
+ Examples
180
+ ========
181
+
182
+ >>> from sympy.abc import x
183
+ >>> from sympy.codegen.cfunctions import exp2
184
+ >>> exp2(2).evalf() == 4.0
185
+ True
186
+ >>> exp2(x).diff(x)
187
+ log(2)*exp2(x)
188
+
189
+ See Also
190
+ ========
191
+
192
+ log2
193
+ """
194
+ nargs = 1
195
+
196
+
197
+ def fdiff(self, argindex=1):
198
+ """
199
+ Returns the first derivative of this function.
200
+ """
201
+ if argindex == 1:
202
+ return self*log(_Two)
203
+ else:
204
+ raise ArgumentIndexError(self, argindex)
205
+
206
+ def _eval_rewrite_as_Pow(self, arg, **kwargs):
207
+ return _exp2(arg)
208
+
209
+ _eval_rewrite_as_tractable = _eval_rewrite_as_Pow
210
+
211
+ def _eval_expand_func(self, **hints):
212
+ return _exp2(*self.args)
213
+
214
+ @classmethod
215
+ def eval(cls, arg):
216
+ if arg.is_number:
217
+ return _exp2(arg)
218
+
219
+
220
+ def _log2(x):
221
+ return log(x)/log(_Two)
222
+
223
+
224
+ class log2(Function):
225
+ """
226
+ Represents the logarithm function with base two.
227
+
228
+ Explanation
229
+ ===========
230
+
231
+ The benefit of using ``log2(x)`` over ``log(x)/log(2)``
232
+ is that the latter is not as efficient under finite precision
233
+ arithmetic.
234
+
235
+ Examples
236
+ ========
237
+
238
+ >>> from sympy.abc import x
239
+ >>> from sympy.codegen.cfunctions import log2
240
+ >>> log2(4).evalf() == 2.0
241
+ True
242
+ >>> log2(x).diff(x)
243
+ 1/(x*log(2))
244
+
245
+ See Also
246
+ ========
247
+
248
+ exp2
249
+ log10
250
+ """
251
+ nargs = 1
252
+
253
+ def fdiff(self, argindex=1):
254
+ """
255
+ Returns the first derivative of this function.
256
+ """
257
+ if argindex == 1:
258
+ return S.One/(log(_Two)*self.args[0])
259
+ else:
260
+ raise ArgumentIndexError(self, argindex)
261
+
262
+
263
+ @classmethod
264
+ def eval(cls, arg):
265
+ if arg.is_number:
266
+ result = log.eval(arg, base=_Two)
267
+ if result.is_Atom:
268
+ return result
269
+ elif arg.is_Pow and arg.base == _Two:
270
+ return arg.exp
271
+
272
+ def _eval_evalf(self, *args, **kwargs):
273
+ return self.rewrite(log).evalf(*args, **kwargs)
274
+
275
+ def _eval_expand_func(self, **hints):
276
+ return _log2(*self.args)
277
+
278
+ def _eval_rewrite_as_log(self, arg, **kwargs):
279
+ return _log2(arg)
280
+
281
+ _eval_rewrite_as_tractable = _eval_rewrite_as_log
282
+
283
+
284
+ def _fma(x, y, z):
285
+ return x*y + z
286
+
287
+
288
+ class fma(Function):
289
+ """
290
+ Represents "fused multiply add".
291
+
292
+ Explanation
293
+ ===========
294
+
295
+ The benefit of using ``fma(x, y, z)`` over ``x*y + z``
296
+ is that, under finite precision arithmetic, the former is
297
+ supported by special instructions on some CPUs.
298
+
299
+ Examples
300
+ ========
301
+
302
+ >>> from sympy.abc import x, y, z
303
+ >>> from sympy.codegen.cfunctions import fma
304
+ >>> fma(x, y, z).diff(x)
305
+ y
306
+
307
+ """
308
+ nargs = 3
309
+
310
+ def fdiff(self, argindex=1):
311
+ """
312
+ Returns the first derivative of this function.
313
+ """
314
+ if argindex in (1, 2):
315
+ return self.args[2 - argindex]
316
+ elif argindex == 3:
317
+ return S.One
318
+ else:
319
+ raise ArgumentIndexError(self, argindex)
320
+
321
+
322
+ def _eval_expand_func(self, **hints):
323
+ return _fma(*self.args)
324
+
325
+ def _eval_rewrite_as_tractable(self, arg, limitvar=None, **kwargs):
326
+ return _fma(arg)
327
+
328
+
329
+ _Ten = S(10)
330
+
331
+
332
+ def _log10(x):
333
+ return log(x)/log(_Ten)
334
+
335
+
336
+ class log10(Function):
337
+ """
338
+ Represents the logarithm function with base ten.
339
+
340
+ Examples
341
+ ========
342
+
343
+ >>> from sympy.abc import x
344
+ >>> from sympy.codegen.cfunctions import log10
345
+ >>> log10(100).evalf() == 2.0
346
+ True
347
+ >>> log10(x).diff(x)
348
+ 1/(x*log(10))
349
+
350
+ See Also
351
+ ========
352
+
353
+ log2
354
+ """
355
+ nargs = 1
356
+
357
+ def fdiff(self, argindex=1):
358
+ """
359
+ Returns the first derivative of this function.
360
+ """
361
+ if argindex == 1:
362
+ return S.One/(log(_Ten)*self.args[0])
363
+ else:
364
+ raise ArgumentIndexError(self, argindex)
365
+
366
+
367
+ @classmethod
368
+ def eval(cls, arg):
369
+ if arg.is_number:
370
+ result = log.eval(arg, base=_Ten)
371
+ if result.is_Atom:
372
+ return result
373
+ elif arg.is_Pow and arg.base == _Ten:
374
+ return arg.exp
375
+
376
+ def _eval_expand_func(self, **hints):
377
+ return _log10(*self.args)
378
+
379
+ def _eval_rewrite_as_log(self, arg, **kwargs):
380
+ return _log10(arg)
381
+
382
+ _eval_rewrite_as_tractable = _eval_rewrite_as_log
383
+
384
+
385
+ def _Sqrt(x):
386
+ return Pow(x, S.Half)
387
+
388
+
389
+ class Sqrt(Function): # 'sqrt' already defined in sympy.functions.elementary.miscellaneous
390
+ """
391
+ Represents the square root function.
392
+
393
+ Explanation
394
+ ===========
395
+
396
+ The reason why one would use ``Sqrt(x)`` over ``sqrt(x)``
397
+ is that the latter is internally represented as ``Pow(x, S.Half)`` which
398
+ may not be what one wants when doing code-generation.
399
+
400
+ Examples
401
+ ========
402
+
403
+ >>> from sympy.abc import x
404
+ >>> from sympy.codegen.cfunctions import Sqrt
405
+ >>> Sqrt(x)
406
+ Sqrt(x)
407
+ >>> Sqrt(x).diff(x)
408
+ 1/(2*sqrt(x))
409
+
410
+ See Also
411
+ ========
412
+
413
+ Cbrt
414
+ """
415
+ nargs = 1
416
+
417
+ def fdiff(self, argindex=1):
418
+ """
419
+ Returns the first derivative of this function.
420
+ """
421
+ if argindex == 1:
422
+ return Pow(self.args[0], Rational(-1, 2))/_Two
423
+ else:
424
+ raise ArgumentIndexError(self, argindex)
425
+
426
+ def _eval_expand_func(self, **hints):
427
+ return _Sqrt(*self.args)
428
+
429
+ def _eval_rewrite_as_Pow(self, arg, **kwargs):
430
+ return _Sqrt(arg)
431
+
432
+ _eval_rewrite_as_tractable = _eval_rewrite_as_Pow
433
+
434
+
435
+ def _Cbrt(x):
436
+ return Pow(x, Rational(1, 3))
437
+
438
+
439
+ class Cbrt(Function): # 'cbrt' already defined in sympy.functions.elementary.miscellaneous
440
+ """
441
+ Represents the cube root function.
442
+
443
+ Explanation
444
+ ===========
445
+
446
+ The reason why one would use ``Cbrt(x)`` over ``cbrt(x)``
447
+ is that the latter is internally represented as ``Pow(x, Rational(1, 3))`` which
448
+ may not be what one wants when doing code-generation.
449
+
450
+ Examples
451
+ ========
452
+
453
+ >>> from sympy.abc import x
454
+ >>> from sympy.codegen.cfunctions import Cbrt
455
+ >>> Cbrt(x)
456
+ Cbrt(x)
457
+ >>> Cbrt(x).diff(x)
458
+ 1/(3*x**(2/3))
459
+
460
+ See Also
461
+ ========
462
+
463
+ Sqrt
464
+ """
465
+ nargs = 1
466
+
467
+ def fdiff(self, argindex=1):
468
+ """
469
+ Returns the first derivative of this function.
470
+ """
471
+ if argindex == 1:
472
+ return Pow(self.args[0], Rational(-_Two/3))/3
473
+ else:
474
+ raise ArgumentIndexError(self, argindex)
475
+
476
+
477
+ def _eval_expand_func(self, **hints):
478
+ return _Cbrt(*self.args)
479
+
480
+ def _eval_rewrite_as_Pow(self, arg, **kwargs):
481
+ return _Cbrt(arg)
482
+
483
+ _eval_rewrite_as_tractable = _eval_rewrite_as_Pow
484
+
485
+
486
+ def _hypot(x, y):
487
+ return sqrt(Pow(x, 2) + Pow(y, 2))
488
+
489
+
490
+ class hypot(Function):
491
+ """
492
+ Represents the hypotenuse function.
493
+
494
+ Explanation
495
+ ===========
496
+
497
+ The hypotenuse function is provided by e.g. the math library
498
+ in the C99 standard, hence one may want to represent the function
499
+ symbolically when doing code-generation.
500
+
501
+ Examples
502
+ ========
503
+
504
+ >>> from sympy.abc import x, y
505
+ >>> from sympy.codegen.cfunctions import hypot
506
+ >>> hypot(3, 4).evalf() == 5.0
507
+ True
508
+ >>> hypot(x, y)
509
+ hypot(x, y)
510
+ >>> hypot(x, y).diff(x)
511
+ x/hypot(x, y)
512
+
513
+ """
514
+ nargs = 2
515
+
516
+ def fdiff(self, argindex=1):
517
+ """
518
+ Returns the first derivative of this function.
519
+ """
520
+ if argindex in (1, 2):
521
+ return 2*self.args[argindex-1]/(_Two*self.func(*self.args))
522
+ else:
523
+ raise ArgumentIndexError(self, argindex)
524
+
525
+
526
+ def _eval_expand_func(self, **hints):
527
+ return _hypot(*self.args)
528
+
529
+ def _eval_rewrite_as_Pow(self, arg, **kwargs):
530
+ return _hypot(arg)
531
+
532
+ _eval_rewrite_as_tractable = _eval_rewrite_as_Pow
533
+
534
+
535
+ class isnan(BooleanFunction):
536
+ nargs = 1
537
+
538
+ @classmethod
539
+ def eval(cls, arg):
540
+ if arg is S.NaN:
541
+ return true
542
+ elif arg.is_number:
543
+ return false
544
+ else:
545
+ return None
546
+
547
+
548
+ class isinf(BooleanFunction):
549
+ nargs = 1
550
+
551
+ @classmethod
552
+ def eval(cls, arg):
553
+ if arg.is_infinite:
554
+ return true
555
+ elif arg.is_finite:
556
+ return false
557
+ else:
558
+ return None
.venv/lib/python3.13/site-packages/sympy/codegen/cnodes.py ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ AST nodes specific to the C family of languages
3
+ """
4
+
5
+ from sympy.codegen.ast import (
6
+ Attribute, Declaration, Node, String, Token, Type, none,
7
+ FunctionCall, CodeBlock
8
+ )
9
+ from sympy.core.basic import Basic
10
+ from sympy.core.containers import Tuple
11
+ from sympy.core.sympify import sympify
12
+
13
+ void = Type('void')
14
+
15
+ restrict = Attribute('restrict') # guarantees no pointer aliasing
16
+ volatile = Attribute('volatile')
17
+ static = Attribute('static')
18
+
19
+
20
+ def alignof(arg):
21
+ """ Generate of FunctionCall instance for calling 'alignof' """
22
+ return FunctionCall('alignof', [String(arg) if isinstance(arg, str) else arg])
23
+
24
+
25
+ def sizeof(arg):
26
+ """ Generate of FunctionCall instance for calling 'sizeof'
27
+
28
+ Examples
29
+ ========
30
+
31
+ >>> from sympy.codegen.ast import real
32
+ >>> from sympy.codegen.cnodes import sizeof
33
+ >>> from sympy import ccode
34
+ >>> ccode(sizeof(real))
35
+ 'sizeof(double)'
36
+ """
37
+ return FunctionCall('sizeof', [String(arg) if isinstance(arg, str) else arg])
38
+
39
+
40
+ class CommaOperator(Basic):
41
+ """ Represents the comma operator in C """
42
+ def __new__(cls, *args):
43
+ return Basic.__new__(cls, *[sympify(arg) for arg in args])
44
+
45
+
46
+ class Label(Node):
47
+ """ Label for use with e.g. goto statement.
48
+
49
+ Examples
50
+ ========
51
+
52
+ >>> from sympy import ccode, Symbol
53
+ >>> from sympy.codegen.cnodes import Label, PreIncrement
54
+ >>> print(ccode(Label('foo')))
55
+ foo:
56
+ >>> print(ccode(Label('bar', [PreIncrement(Symbol('a'))])))
57
+ bar:
58
+ ++(a);
59
+
60
+ """
61
+ __slots__ = _fields = ('name', 'body')
62
+ defaults = {'body': none}
63
+ _construct_name = String
64
+
65
+ @classmethod
66
+ def _construct_body(cls, itr):
67
+ if isinstance(itr, CodeBlock):
68
+ return itr
69
+ else:
70
+ return CodeBlock(*itr)
71
+
72
+
73
+ class goto(Token):
74
+ """ Represents goto in C """
75
+ __slots__ = _fields = ('label',)
76
+ _construct_label = Label
77
+
78
+
79
+ class PreDecrement(Basic):
80
+ """ Represents the pre-decrement operator
81
+
82
+ Examples
83
+ ========
84
+
85
+ >>> from sympy.abc import x
86
+ >>> from sympy.codegen.cnodes import PreDecrement
87
+ >>> from sympy import ccode
88
+ >>> ccode(PreDecrement(x))
89
+ '--(x)'
90
+
91
+ """
92
+ nargs = 1
93
+
94
+
95
+ class PostDecrement(Basic):
96
+ """ Represents the post-decrement operator
97
+
98
+ Examples
99
+ ========
100
+
101
+ >>> from sympy.abc import x
102
+ >>> from sympy.codegen.cnodes import PostDecrement
103
+ >>> from sympy import ccode
104
+ >>> ccode(PostDecrement(x))
105
+ '(x)--'
106
+
107
+ """
108
+ nargs = 1
109
+
110
+
111
+ class PreIncrement(Basic):
112
+ """ Represents the pre-increment operator
113
+
114
+ Examples
115
+ ========
116
+
117
+ >>> from sympy.abc import x
118
+ >>> from sympy.codegen.cnodes import PreIncrement
119
+ >>> from sympy import ccode
120
+ >>> ccode(PreIncrement(x))
121
+ '++(x)'
122
+
123
+ """
124
+ nargs = 1
125
+
126
+
127
+ class PostIncrement(Basic):
128
+ """ Represents the post-increment operator
129
+
130
+ Examples
131
+ ========
132
+
133
+ >>> from sympy.abc import x
134
+ >>> from sympy.codegen.cnodes import PostIncrement
135
+ >>> from sympy import ccode
136
+ >>> ccode(PostIncrement(x))
137
+ '(x)++'
138
+
139
+ """
140
+ nargs = 1
141
+
142
+
143
+ class struct(Node):
144
+ """ Represents a struct in C """
145
+ __slots__ = _fields = ('name', 'declarations')
146
+ defaults = {'name': none}
147
+ _construct_name = String
148
+
149
+ @classmethod
150
+ def _construct_declarations(cls, args):
151
+ return Tuple(*[Declaration(arg) for arg in args])
152
+
153
+
154
+ class union(struct):
155
+ """ Represents a union in C """
156
+ __slots__ = ()
.venv/lib/python3.13/site-packages/sympy/codegen/cutils.py ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ from sympy.printing.c import C99CodePrinter
2
+
3
+ def render_as_source_file(content, Printer=C99CodePrinter, settings=None):
4
+ """ Renders a C source file (with required #include statements) """
5
+ printer = Printer(settings or {})
6
+ code_str = printer.doprint(content)
7
+ includes = '\n'.join(['#include <%s>' % h for h in printer.headers])
8
+ return includes + '\n\n' + code_str
.venv/lib/python3.13/site-packages/sympy/codegen/cxxnodes.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ AST nodes specific to C++.
3
+ """
4
+
5
+ from sympy.codegen.ast import Attribute, String, Token, Type, none
6
+
7
+ class using(Token):
8
+ """ Represents a 'using' statement in C++ """
9
+ __slots__ = _fields = ('type', 'alias')
10
+ defaults = {'alias': none}
11
+ _construct_type = Type
12
+ _construct_alias = String
13
+
14
+ constexpr = Attribute('constexpr')
.venv/lib/python3.13/site-packages/sympy/codegen/fnodes.py ADDED
@@ -0,0 +1,658 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ AST nodes specific to Fortran.
3
+
4
+ The functions defined in this module allows the user to express functions such as ``dsign``
5
+ as a SymPy function for symbolic manipulation.
6
+ """
7
+
8
+ from __future__ import annotations
9
+ from sympy.codegen.ast import (
10
+ Attribute, CodeBlock, FunctionCall, Node, none, String,
11
+ Token, _mk_Tuple, Variable
12
+ )
13
+ from sympy.core.basic import Basic
14
+ from sympy.core.containers import Tuple
15
+ from sympy.core.expr import Expr
16
+ from sympy.core.function import Function
17
+ from sympy.core.numbers import Float, Integer
18
+ from sympy.core.symbol import Str
19
+ from sympy.core.sympify import sympify
20
+ from sympy.logic import true, false
21
+ from sympy.utilities.iterables import iterable
22
+
23
+
24
+
25
+ pure = Attribute('pure')
26
+ elemental = Attribute('elemental') # (all elemental procedures are also pure)
27
+
28
+ intent_in = Attribute('intent_in')
29
+ intent_out = Attribute('intent_out')
30
+ intent_inout = Attribute('intent_inout')
31
+
32
+ allocatable = Attribute('allocatable')
33
+
34
+ class Program(Token):
35
+ """ Represents a 'program' block in Fortran.
36
+
37
+ Examples
38
+ ========
39
+
40
+ >>> from sympy.codegen.ast import Print
41
+ >>> from sympy.codegen.fnodes import Program
42
+ >>> prog = Program('myprogram', [Print([42])])
43
+ >>> from sympy import fcode
44
+ >>> print(fcode(prog, source_format='free'))
45
+ program myprogram
46
+ print *, 42
47
+ end program
48
+
49
+ """
50
+ __slots__ = _fields = ('name', 'body')
51
+ _construct_name = String
52
+ _construct_body = staticmethod(lambda body: CodeBlock(*body))
53
+
54
+
55
+ class use_rename(Token):
56
+ """ Represents a renaming in a use statement in Fortran.
57
+
58
+ Examples
59
+ ========
60
+
61
+ >>> from sympy.codegen.fnodes import use_rename, use
62
+ >>> from sympy import fcode
63
+ >>> ren = use_rename("thingy", "convolution2d")
64
+ >>> print(fcode(ren, source_format='free'))
65
+ thingy => convolution2d
66
+ >>> full = use('signallib', only=['snr', ren])
67
+ >>> print(fcode(full, source_format='free'))
68
+ use signallib, only: snr, thingy => convolution2d
69
+
70
+ """
71
+ __slots__ = _fields = ('local', 'original')
72
+ _construct_local = String
73
+ _construct_original = String
74
+
75
+ def _name(arg):
76
+ if hasattr(arg, 'name'):
77
+ return arg.name
78
+ else:
79
+ return String(arg)
80
+
81
+ class use(Token):
82
+ """ Represents a use statement in Fortran.
83
+
84
+ Examples
85
+ ========
86
+
87
+ >>> from sympy.codegen.fnodes import use
88
+ >>> from sympy import fcode
89
+ >>> fcode(use('signallib'), source_format='free')
90
+ 'use signallib'
91
+ >>> fcode(use('signallib', [('metric', 'snr')]), source_format='free')
92
+ 'use signallib, metric => snr'
93
+ >>> fcode(use('signallib', only=['snr', 'convolution2d']), source_format='free')
94
+ 'use signallib, only: snr, convolution2d'
95
+
96
+ """
97
+ __slots__ = _fields = ('namespace', 'rename', 'only')
98
+ defaults = {'rename': none, 'only': none}
99
+ _construct_namespace = staticmethod(_name)
100
+ _construct_rename = staticmethod(lambda args: Tuple(*[arg if isinstance(arg, use_rename) else use_rename(*arg) for arg in args]))
101
+ _construct_only = staticmethod(lambda args: Tuple(*[arg if isinstance(arg, use_rename) else _name(arg) for arg in args]))
102
+
103
+
104
+ class Module(Token):
105
+ """ Represents a module in Fortran.
106
+
107
+ Examples
108
+ ========
109
+
110
+ >>> from sympy.codegen.fnodes import Module
111
+ >>> from sympy import fcode
112
+ >>> print(fcode(Module('signallib', ['implicit none'], []), source_format='free'))
113
+ module signallib
114
+ implicit none
115
+ <BLANKLINE>
116
+ contains
117
+ <BLANKLINE>
118
+ <BLANKLINE>
119
+ end module
120
+
121
+ """
122
+ __slots__ = _fields = ('name', 'declarations', 'definitions')
123
+ defaults = {'declarations': Tuple()}
124
+ _construct_name = String
125
+
126
+ @classmethod
127
+ def _construct_declarations(cls, args):
128
+ args = [Str(arg) if isinstance(arg, str) else arg for arg in args]
129
+ return CodeBlock(*args)
130
+
131
+ _construct_definitions = staticmethod(lambda arg: CodeBlock(*arg))
132
+
133
+
134
+ class Subroutine(Node):
135
+ """ Represents a subroutine in Fortran.
136
+
137
+ Examples
138
+ ========
139
+
140
+ >>> from sympy import fcode, symbols
141
+ >>> from sympy.codegen.ast import Print
142
+ >>> from sympy.codegen.fnodes import Subroutine
143
+ >>> x, y = symbols('x y', real=True)
144
+ >>> sub = Subroutine('mysub', [x, y], [Print([x**2 + y**2, x*y])])
145
+ >>> print(fcode(sub, source_format='free', standard=2003))
146
+ subroutine mysub(x, y)
147
+ real*8 :: x
148
+ real*8 :: y
149
+ print *, x**2 + y**2, x*y
150
+ end subroutine
151
+
152
+ """
153
+ __slots__ = ('name', 'parameters', 'body')
154
+ _fields = __slots__ + Node._fields
155
+ _construct_name = String
156
+ _construct_parameters = staticmethod(lambda params: Tuple(*map(Variable.deduced, params)))
157
+
158
+ @classmethod
159
+ def _construct_body(cls, itr):
160
+ if isinstance(itr, CodeBlock):
161
+ return itr
162
+ else:
163
+ return CodeBlock(*itr)
164
+
165
+ class SubroutineCall(Token):
166
+ """ Represents a call to a subroutine in Fortran.
167
+
168
+ Examples
169
+ ========
170
+
171
+ >>> from sympy.codegen.fnodes import SubroutineCall
172
+ >>> from sympy import fcode
173
+ >>> fcode(SubroutineCall('mysub', 'x y'.split()))
174
+ ' call mysub(x, y)'
175
+
176
+ """
177
+ __slots__ = _fields = ('name', 'subroutine_args')
178
+ _construct_name = staticmethod(_name)
179
+ _construct_subroutine_args = staticmethod(_mk_Tuple)
180
+
181
+
182
+ class Do(Token):
183
+ """ Represents a Do loop in in Fortran.
184
+
185
+ Examples
186
+ ========
187
+
188
+ >>> from sympy import fcode, symbols
189
+ >>> from sympy.codegen.ast import aug_assign, Print
190
+ >>> from sympy.codegen.fnodes import Do
191
+ >>> i, n = symbols('i n', integer=True)
192
+ >>> r = symbols('r', real=True)
193
+ >>> body = [aug_assign(r, '+', 1/i), Print([i, r])]
194
+ >>> do1 = Do(body, i, 1, n)
195
+ >>> print(fcode(do1, source_format='free'))
196
+ do i = 1, n
197
+ r = r + 1d0/i
198
+ print *, i, r
199
+ end do
200
+ >>> do2 = Do(body, i, 1, n, 2)
201
+ >>> print(fcode(do2, source_format='free'))
202
+ do i = 1, n, 2
203
+ r = r + 1d0/i
204
+ print *, i, r
205
+ end do
206
+
207
+ """
208
+
209
+ __slots__ = _fields = ('body', 'counter', 'first', 'last', 'step', 'concurrent')
210
+ defaults = {'step': Integer(1), 'concurrent': false}
211
+ _construct_body = staticmethod(lambda body: CodeBlock(*body))
212
+ _construct_counter = staticmethod(sympify)
213
+ _construct_first = staticmethod(sympify)
214
+ _construct_last = staticmethod(sympify)
215
+ _construct_step = staticmethod(sympify)
216
+ _construct_concurrent = staticmethod(lambda arg: true if arg else false)
217
+
218
+
219
+ class ArrayConstructor(Token):
220
+ """ Represents an array constructor.
221
+
222
+ Examples
223
+ ========
224
+
225
+ >>> from sympy import fcode
226
+ >>> from sympy.codegen.fnodes import ArrayConstructor
227
+ >>> ac = ArrayConstructor([1, 2, 3])
228
+ >>> fcode(ac, standard=95, source_format='free')
229
+ '(/1, 2, 3/)'
230
+ >>> fcode(ac, standard=2003, source_format='free')
231
+ '[1, 2, 3]'
232
+
233
+ """
234
+ __slots__ = _fields = ('elements',)
235
+ _construct_elements = staticmethod(_mk_Tuple)
236
+
237
+
238
+ class ImpliedDoLoop(Token):
239
+ """ Represents an implied do loop in Fortran.
240
+
241
+ Examples
242
+ ========
243
+
244
+ >>> from sympy import Symbol, fcode
245
+ >>> from sympy.codegen.fnodes import ImpliedDoLoop, ArrayConstructor
246
+ >>> i = Symbol('i', integer=True)
247
+ >>> idl = ImpliedDoLoop(i**3, i, -3, 3, 2) # -27, -1, 1, 27
248
+ >>> ac = ArrayConstructor([-28, idl, 28]) # -28, -27, -1, 1, 27, 28
249
+ >>> fcode(ac, standard=2003, source_format='free')
250
+ '[-28, (i**3, i = -3, 3, 2), 28]'
251
+
252
+ """
253
+ __slots__ = _fields = ('expr', 'counter', 'first', 'last', 'step')
254
+ defaults = {'step': Integer(1)}
255
+ _construct_expr = staticmethod(sympify)
256
+ _construct_counter = staticmethod(sympify)
257
+ _construct_first = staticmethod(sympify)
258
+ _construct_last = staticmethod(sympify)
259
+ _construct_step = staticmethod(sympify)
260
+
261
+
262
+ class Extent(Basic):
263
+ """ Represents a dimension extent.
264
+
265
+ Examples
266
+ ========
267
+
268
+ >>> from sympy.codegen.fnodes import Extent
269
+ >>> e = Extent(-3, 3) # -3, -2, -1, 0, 1, 2, 3
270
+ >>> from sympy import fcode
271
+ >>> fcode(e, source_format='free')
272
+ '-3:3'
273
+ >>> from sympy.codegen.ast import Variable, real
274
+ >>> from sympy.codegen.fnodes import dimension, intent_out
275
+ >>> dim = dimension(e, e)
276
+ >>> arr = Variable('x', real, attrs=[dim, intent_out])
277
+ >>> fcode(arr.as_Declaration(), source_format='free', standard=2003)
278
+ 'real*8, dimension(-3:3, -3:3), intent(out) :: x'
279
+
280
+ """
281
+ def __new__(cls, *args):
282
+ if len(args) == 2:
283
+ low, high = args
284
+ return Basic.__new__(cls, sympify(low), sympify(high))
285
+ elif len(args) == 0 or (len(args) == 1 and args[0] in (':', None)):
286
+ return Basic.__new__(cls) # assumed shape
287
+ else:
288
+ raise ValueError("Expected 0 or 2 args (or one argument == None or ':')")
289
+
290
+ def _sympystr(self, printer):
291
+ if len(self.args) == 0:
292
+ return ':'
293
+ return ":".join(str(arg) for arg in self.args)
294
+
295
+ assumed_extent = Extent() # or Extent(':'), Extent(None)
296
+
297
+
298
+ def dimension(*args):
299
+ """ Creates a 'dimension' Attribute with (up to 7) extents.
300
+
301
+ Examples
302
+ ========
303
+
304
+ >>> from sympy import fcode
305
+ >>> from sympy.codegen.fnodes import dimension, intent_in
306
+ >>> dim = dimension('2', ':') # 2 rows, runtime determined number of columns
307
+ >>> from sympy.codegen.ast import Variable, integer
308
+ >>> arr = Variable('a', integer, attrs=[dim, intent_in])
309
+ >>> fcode(arr.as_Declaration(), source_format='free', standard=2003)
310
+ 'integer*4, dimension(2, :), intent(in) :: a'
311
+
312
+ """
313
+ if len(args) > 7:
314
+ raise ValueError("Fortran only supports up to 7 dimensional arrays")
315
+ parameters = []
316
+ for arg in args:
317
+ if isinstance(arg, Extent):
318
+ parameters.append(arg)
319
+ elif isinstance(arg, str):
320
+ if arg == ':':
321
+ parameters.append(Extent())
322
+ else:
323
+ parameters.append(String(arg))
324
+ elif iterable(arg):
325
+ parameters.append(Extent(*arg))
326
+ else:
327
+ parameters.append(sympify(arg))
328
+ if len(args) == 0:
329
+ raise ValueError("Need at least one dimension")
330
+ return Attribute('dimension', parameters)
331
+
332
+
333
+ assumed_size = dimension('*')
334
+
335
+ def array(symbol, dim, intent=None, *, attrs=(), value=None, type=None):
336
+ """ Convenience function for creating a Variable instance for a Fortran array.
337
+
338
+ Parameters
339
+ ==========
340
+
341
+ symbol : symbol
342
+ dim : Attribute or iterable
343
+ If dim is an ``Attribute`` it need to have the name 'dimension'. If it is
344
+ not an ``Attribute``, then it is passed to :func:`dimension` as ``*dim``
345
+ intent : str
346
+ One of: 'in', 'out', 'inout' or None
347
+ \\*\\*kwargs:
348
+ Keyword arguments for ``Variable`` ('type' & 'value')
349
+
350
+ Examples
351
+ ========
352
+
353
+ >>> from sympy import fcode
354
+ >>> from sympy.codegen.ast import integer, real
355
+ >>> from sympy.codegen.fnodes import array
356
+ >>> arr = array('a', '*', 'in', type=integer)
357
+ >>> print(fcode(arr.as_Declaration(), source_format='free', standard=2003))
358
+ integer*4, dimension(*), intent(in) :: a
359
+ >>> x = array('x', [3, ':', ':'], intent='out', type=real)
360
+ >>> print(fcode(x.as_Declaration(value=1), source_format='free', standard=2003))
361
+ real*8, dimension(3, :, :), intent(out) :: x = 1
362
+
363
+ """
364
+ if isinstance(dim, Attribute):
365
+ if str(dim.name) != 'dimension':
366
+ raise ValueError("Got an unexpected Attribute argument as dim: %s" % str(dim))
367
+ else:
368
+ dim = dimension(*dim)
369
+
370
+ attrs = list(attrs) + [dim]
371
+ if intent is not None:
372
+ if intent not in (intent_in, intent_out, intent_inout):
373
+ intent = {'in': intent_in, 'out': intent_out, 'inout': intent_inout}[intent]
374
+ attrs.append(intent)
375
+ if type is None:
376
+ return Variable.deduced(symbol, value=value, attrs=attrs)
377
+ else:
378
+ return Variable(symbol, type, value=value, attrs=attrs)
379
+
380
+ def _printable(arg):
381
+ return String(arg) if isinstance(arg, str) else sympify(arg)
382
+
383
+
384
+ def allocated(array):
385
+ """ Creates an AST node for a function call to Fortran's "allocated(...)"
386
+
387
+ Examples
388
+ ========
389
+
390
+ >>> from sympy import fcode
391
+ >>> from sympy.codegen.fnodes import allocated
392
+ >>> alloc = allocated('x')
393
+ >>> fcode(alloc, source_format='free')
394
+ 'allocated(x)'
395
+
396
+ """
397
+ return FunctionCall('allocated', [_printable(array)])
398
+
399
+
400
+ def lbound(array, dim=None, kind=None):
401
+ """ Creates an AST node for a function call to Fortran's "lbound(...)"
402
+
403
+ Parameters
404
+ ==========
405
+
406
+ array : Symbol or String
407
+ dim : expr
408
+ kind : expr
409
+
410
+ Examples
411
+ ========
412
+
413
+ >>> from sympy import fcode
414
+ >>> from sympy.codegen.fnodes import lbound
415
+ >>> lb = lbound('arr', dim=2)
416
+ >>> fcode(lb, source_format='free')
417
+ 'lbound(arr, 2)'
418
+
419
+ """
420
+ return FunctionCall(
421
+ 'lbound',
422
+ [_printable(array)] +
423
+ ([_printable(dim)] if dim else []) +
424
+ ([_printable(kind)] if kind else [])
425
+ )
426
+
427
+
428
+ def ubound(array, dim=None, kind=None):
429
+ return FunctionCall(
430
+ 'ubound',
431
+ [_printable(array)] +
432
+ ([_printable(dim)] if dim else []) +
433
+ ([_printable(kind)] if kind else [])
434
+ )
435
+
436
+
437
+ def shape(source, kind=None):
438
+ """ Creates an AST node for a function call to Fortran's "shape(...)"
439
+
440
+ Parameters
441
+ ==========
442
+
443
+ source : Symbol or String
444
+ kind : expr
445
+
446
+ Examples
447
+ ========
448
+
449
+ >>> from sympy import fcode
450
+ >>> from sympy.codegen.fnodes import shape
451
+ >>> shp = shape('x')
452
+ >>> fcode(shp, source_format='free')
453
+ 'shape(x)'
454
+
455
+ """
456
+ return FunctionCall(
457
+ 'shape',
458
+ [_printable(source)] +
459
+ ([_printable(kind)] if kind else [])
460
+ )
461
+
462
+
463
+ def size(array, dim=None, kind=None):
464
+ """ Creates an AST node for a function call to Fortran's "size(...)"
465
+
466
+ Examples
467
+ ========
468
+
469
+ >>> from sympy import fcode, Symbol
470
+ >>> from sympy.codegen.ast import FunctionDefinition, real, Return
471
+ >>> from sympy.codegen.fnodes import array, sum_, size
472
+ >>> a = Symbol('a', real=True)
473
+ >>> body = [Return((sum_(a**2)/size(a))**.5)]
474
+ >>> arr = array(a, dim=[':'], intent='in')
475
+ >>> fd = FunctionDefinition(real, 'rms', [arr], body)
476
+ >>> print(fcode(fd, source_format='free', standard=2003))
477
+ real*8 function rms(a)
478
+ real*8, dimension(:), intent(in) :: a
479
+ rms = sqrt(sum(a**2)*1d0/size(a))
480
+ end function
481
+
482
+ """
483
+ return FunctionCall(
484
+ 'size',
485
+ [_printable(array)] +
486
+ ([_printable(dim)] if dim else []) +
487
+ ([_printable(kind)] if kind else [])
488
+ )
489
+
490
+
491
+ def reshape(source, shape, pad=None, order=None):
492
+ """ Creates an AST node for a function call to Fortran's "reshape(...)"
493
+
494
+ Parameters
495
+ ==========
496
+
497
+ source : Symbol or String
498
+ shape : ArrayExpr
499
+
500
+ """
501
+ return FunctionCall(
502
+ 'reshape',
503
+ [_printable(source), _printable(shape)] +
504
+ ([_printable(pad)] if pad else []) +
505
+ ([_printable(order)] if pad else [])
506
+ )
507
+
508
+
509
+ def bind_C(name=None):
510
+ """ Creates an Attribute ``bind_C`` with a name.
511
+
512
+ Parameters
513
+ ==========
514
+
515
+ name : str
516
+
517
+ Examples
518
+ ========
519
+
520
+ >>> from sympy import fcode, Symbol
521
+ >>> from sympy.codegen.ast import FunctionDefinition, real, Return
522
+ >>> from sympy.codegen.fnodes import array, sum_, bind_C
523
+ >>> a = Symbol('a', real=True)
524
+ >>> s = Symbol('s', integer=True)
525
+ >>> arr = array(a, dim=[s], intent='in')
526
+ >>> body = [Return((sum_(a**2)/s)**.5)]
527
+ >>> fd = FunctionDefinition(real, 'rms', [arr, s], body, attrs=[bind_C('rms')])
528
+ >>> print(fcode(fd, source_format='free', standard=2003))
529
+ real*8 function rms(a, s) bind(C, name="rms")
530
+ real*8, dimension(s), intent(in) :: a
531
+ integer*4 :: s
532
+ rms = sqrt(sum(a**2)/s)
533
+ end function
534
+
535
+ """
536
+ return Attribute('bind_C', [String(name)] if name else [])
537
+
538
+ class GoTo(Token):
539
+ """ Represents a goto statement in Fortran
540
+
541
+ Examples
542
+ ========
543
+
544
+ >>> from sympy.codegen.fnodes import GoTo
545
+ >>> go = GoTo([10, 20, 30], 'i')
546
+ >>> from sympy import fcode
547
+ >>> fcode(go, source_format='free')
548
+ 'go to (10, 20, 30), i'
549
+
550
+ """
551
+ __slots__ = _fields = ('labels', 'expr')
552
+ defaults = {'expr': none}
553
+ _construct_labels = staticmethod(_mk_Tuple)
554
+ _construct_expr = staticmethod(sympify)
555
+
556
+
557
+ class FortranReturn(Token):
558
+ """ AST node explicitly mapped to a fortran "return".
559
+
560
+ Explanation
561
+ ===========
562
+
563
+ Because a return statement in fortran is different from C, and
564
+ in order to aid reuse of our codegen ASTs the ordinary
565
+ ``.codegen.ast.Return`` is interpreted as assignment to
566
+ the result variable of the function. If one for some reason needs
567
+ to generate a fortran RETURN statement, this node should be used.
568
+
569
+ Examples
570
+ ========
571
+
572
+ >>> from sympy.codegen.fnodes import FortranReturn
573
+ >>> from sympy import fcode
574
+ >>> fcode(FortranReturn('x'))
575
+ ' return x'
576
+
577
+ """
578
+ __slots__ = _fields = ('return_value',)
579
+ defaults = {'return_value': none}
580
+ _construct_return_value = staticmethod(sympify)
581
+
582
+
583
+ class FFunction(Function):
584
+ _required_standard = 77
585
+
586
+ def _fcode(self, printer):
587
+ name = self.__class__.__name__
588
+ if printer._settings['standard'] < self._required_standard:
589
+ raise NotImplementedError("%s requires Fortran %d or newer" %
590
+ (name, self._required_standard))
591
+ return '{}({})'.format(name, ', '.join(map(printer._print, self.args)))
592
+
593
+
594
+ class F95Function(FFunction):
595
+ _required_standard = 95
596
+
597
+
598
+ class isign(FFunction):
599
+ """ Fortran sign intrinsic for integer arguments. """
600
+ nargs = 2
601
+
602
+
603
+ class dsign(FFunction):
604
+ """ Fortran sign intrinsic for double precision arguments. """
605
+ nargs = 2
606
+
607
+
608
+ class cmplx(FFunction):
609
+ """ Fortran complex conversion function. """
610
+ nargs = 2 # may be extended to (2, 3) at a later point
611
+
612
+
613
+ class kind(FFunction):
614
+ """ Fortran kind function. """
615
+ nargs = 1
616
+
617
+
618
+ class merge(F95Function):
619
+ """ Fortran merge function """
620
+ nargs = 3
621
+
622
+
623
+ class _literal(Float):
624
+ _token: str
625
+ _decimals: int
626
+
627
+ def _fcode(self, printer, *args, **kwargs):
628
+ mantissa, sgnd_ex = ('%.{}e'.format(self._decimals) % self).split('e')
629
+ mantissa = mantissa.strip('0').rstrip('.')
630
+ ex_sgn, ex_num = sgnd_ex[0], sgnd_ex[1:].lstrip('0')
631
+ ex_sgn = '' if ex_sgn == '+' else ex_sgn
632
+ return (mantissa or '0') + self._token + ex_sgn + (ex_num or '0')
633
+
634
+
635
+ class literal_sp(_literal):
636
+ """ Fortran single precision real literal """
637
+ _token = 'e'
638
+ _decimals = 9
639
+
640
+
641
+ class literal_dp(_literal):
642
+ """ Fortran double precision real literal """
643
+ _token = 'd'
644
+ _decimals = 17
645
+
646
+
647
+ class sum_(Token, Expr):
648
+ __slots__ = _fields = ('array', 'dim', 'mask')
649
+ defaults = {'dim': none, 'mask': none}
650
+ _construct_array = staticmethod(sympify)
651
+ _construct_dim = staticmethod(sympify)
652
+
653
+
654
+ class product_(Token, Expr):
655
+ __slots__ = _fields = ('array', 'dim', 'mask')
656
+ defaults = {'dim': none, 'mask': none}
657
+ _construct_array = staticmethod(sympify)
658
+ _construct_dim = staticmethod(sympify)
.venv/lib/python3.13/site-packages/sympy/codegen/futils.py ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from itertools import chain
2
+ from sympy.codegen.fnodes import Module
3
+ from sympy.core.symbol import Dummy
4
+ from sympy.printing.fortran import FCodePrinter
5
+
6
+ """ This module collects utilities for rendering Fortran code. """
7
+
8
+
9
+ def render_as_module(definitions, name, declarations=(), printer_settings=None):
10
+ """ Creates a ``Module`` instance and renders it as a string.
11
+
12
+ This generates Fortran source code for a module with the correct ``use`` statements.
13
+
14
+ Parameters
15
+ ==========
16
+
17
+ definitions : iterable
18
+ Passed to :class:`sympy.codegen.fnodes.Module`.
19
+ name : str
20
+ Passed to :class:`sympy.codegen.fnodes.Module`.
21
+ declarations : iterable
22
+ Passed to :class:`sympy.codegen.fnodes.Module`. It will be extended with
23
+ use statements, 'implicit none' and public list generated from ``definitions``.
24
+ printer_settings : dict
25
+ Passed to ``FCodePrinter`` (default: ``{'standard': 2003, 'source_format': 'free'}``).
26
+
27
+ """
28
+ printer_settings = printer_settings or {'standard': 2003, 'source_format': 'free'}
29
+ printer = FCodePrinter(printer_settings)
30
+ dummy = Dummy()
31
+ if isinstance(definitions, Module):
32
+ raise ValueError("This function expects to construct a module on its own.")
33
+ mod = Module(name, chain(declarations, [dummy]), definitions)
34
+ fstr = printer.doprint(mod)
35
+ module_use_str = ' %s\n' % ' \n'.join(['use %s, only: %s' % (k, ', '.join(v)) for
36
+ k, v in printer.module_uses.items()])
37
+ module_use_str += ' implicit none\n'
38
+ module_use_str += ' private\n'
39
+ module_use_str += ' public %s\n' % ', '.join([str(node.name) for node in definitions if getattr(node, 'name', None)])
40
+ return fstr.replace(printer.doprint(dummy), module_use_str)
.venv/lib/python3.13/site-packages/sympy/codegen/matrix_nodes.py ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Additional AST nodes for operations on matrices. The nodes in this module
3
+ are meant to represent optimization of matrix expressions within codegen's
4
+ target languages that cannot be represented by SymPy expressions.
5
+
6
+ As an example, we can use :meth:`sympy.codegen.rewriting.optimize` and the
7
+ ``matin_opt`` optimization provided in :mod:`sympy.codegen.rewriting` to
8
+ transform matrix multiplication under certain assumptions:
9
+
10
+ >>> from sympy import symbols, MatrixSymbol
11
+ >>> n = symbols('n', integer=True)
12
+ >>> A = MatrixSymbol('A', n, n)
13
+ >>> x = MatrixSymbol('x', n, 1)
14
+ >>> expr = A**(-1) * x
15
+ >>> from sympy import assuming, Q
16
+ >>> from sympy.codegen.rewriting import matinv_opt, optimize
17
+ >>> with assuming(Q.fullrank(A)):
18
+ ... optimize(expr, [matinv_opt])
19
+ MatrixSolve(A, vector=x)
20
+ """
21
+
22
+ from .ast import Token
23
+ from sympy.matrices import MatrixExpr
24
+ from sympy.core.sympify import sympify
25
+
26
+
27
+ class MatrixSolve(Token, MatrixExpr):
28
+ """Represents an operation to solve a linear matrix equation.
29
+
30
+ Parameters
31
+ ==========
32
+
33
+ matrix : MatrixSymbol
34
+
35
+ Matrix representing the coefficients of variables in the linear
36
+ equation. This matrix must be square and full-rank (i.e. all columns must
37
+ be linearly independent) for the solving operation to be valid.
38
+
39
+ vector : MatrixSymbol
40
+
41
+ One-column matrix representing the solutions to the equations
42
+ represented in ``matrix``.
43
+
44
+ Examples
45
+ ========
46
+
47
+ >>> from sympy import symbols, MatrixSymbol
48
+ >>> from sympy.codegen.matrix_nodes import MatrixSolve
49
+ >>> n = symbols('n', integer=True)
50
+ >>> A = MatrixSymbol('A', n, n)
51
+ >>> x = MatrixSymbol('x', n, 1)
52
+ >>> from sympy.printing.numpy import NumPyPrinter
53
+ >>> NumPyPrinter().doprint(MatrixSolve(A, x))
54
+ 'numpy.linalg.solve(A, x)'
55
+ >>> from sympy import octave_code
56
+ >>> octave_code(MatrixSolve(A, x))
57
+ 'A \\\\ x'
58
+
59
+ """
60
+ __slots__ = _fields = ('matrix', 'vector')
61
+
62
+ _construct_matrix = staticmethod(sympify)
63
+ _construct_vector = staticmethod(sympify)
64
+
65
+ @property
66
+ def shape(self):
67
+ return self.vector.shape
68
+
69
+ def _eval_derivative(self, x):
70
+ A, b = self.matrix, self.vector
71
+ return MatrixSolve(A, b.diff(x) - A.diff(x) * MatrixSolve(A, b))
.venv/lib/python3.13/site-packages/sympy/codegen/numpy_nodes.py ADDED
@@ -0,0 +1,177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sympy.core.function import Add, ArgumentIndexError, Function
2
+ from sympy.core.power import Pow
3
+ from sympy.core.singleton import S
4
+ from sympy.core.sorting import default_sort_key
5
+ from sympy.core.sympify import sympify
6
+ from sympy.functions.elementary.exponential import exp, log
7
+ from sympy.functions.elementary.miscellaneous import Max, Min
8
+ from .ast import Token, none
9
+
10
+
11
+ def _logaddexp(x1, x2, *, evaluate=True):
12
+ return log(Add(exp(x1, evaluate=evaluate), exp(x2, evaluate=evaluate), evaluate=evaluate))
13
+
14
+
15
+ _two = S.One*2
16
+ _ln2 = log(_two)
17
+
18
+
19
+ def _lb(x, *, evaluate=True):
20
+ return log(x, evaluate=evaluate)/_ln2
21
+
22
+
23
+ def _exp2(x, *, evaluate=True):
24
+ return Pow(_two, x, evaluate=evaluate)
25
+
26
+
27
+ def _logaddexp2(x1, x2, *, evaluate=True):
28
+ return _lb(Add(_exp2(x1, evaluate=evaluate),
29
+ _exp2(x2, evaluate=evaluate), evaluate=evaluate))
30
+
31
+
32
+ class logaddexp(Function):
33
+ """ Logarithm of the sum of exponentiations of the inputs.
34
+
35
+ Helper class for use with e.g. numpy.logaddexp
36
+
37
+ See Also
38
+ ========
39
+
40
+ https://numpy.org/doc/stable/reference/generated/numpy.logaddexp.html
41
+ """
42
+ nargs = 2
43
+
44
+ def __new__(cls, *args):
45
+ return Function.__new__(cls, *sorted(args, key=default_sort_key))
46
+
47
+ def fdiff(self, argindex=1):
48
+ """
49
+ Returns the first derivative of this function.
50
+ """
51
+ if argindex == 1:
52
+ wrt, other = self.args
53
+ elif argindex == 2:
54
+ other, wrt = self.args
55
+ else:
56
+ raise ArgumentIndexError(self, argindex)
57
+ return S.One/(S.One + exp(other-wrt))
58
+
59
+ def _eval_rewrite_as_log(self, x1, x2, **kwargs):
60
+ return _logaddexp(x1, x2)
61
+
62
+ def _eval_evalf(self, *args, **kwargs):
63
+ return self.rewrite(log).evalf(*args, **kwargs)
64
+
65
+ def _eval_simplify(self, *args, **kwargs):
66
+ a, b = (x.simplify(**kwargs) for x in self.args)
67
+ candidate = _logaddexp(a, b)
68
+ if candidate != _logaddexp(a, b, evaluate=False):
69
+ return candidate
70
+ else:
71
+ return logaddexp(a, b)
72
+
73
+
74
+ class logaddexp2(Function):
75
+ """ Logarithm of the sum of exponentiations of the inputs in base-2.
76
+
77
+ Helper class for use with e.g. numpy.logaddexp2
78
+
79
+ See Also
80
+ ========
81
+
82
+ https://numpy.org/doc/stable/reference/generated/numpy.logaddexp2.html
83
+ """
84
+ nargs = 2
85
+
86
+ def __new__(cls, *args):
87
+ return Function.__new__(cls, *sorted(args, key=default_sort_key))
88
+
89
+ def fdiff(self, argindex=1):
90
+ """
91
+ Returns the first derivative of this function.
92
+ """
93
+ if argindex == 1:
94
+ wrt, other = self.args
95
+ elif argindex == 2:
96
+ other, wrt = self.args
97
+ else:
98
+ raise ArgumentIndexError(self, argindex)
99
+ return S.One/(S.One + _exp2(other-wrt))
100
+
101
+ def _eval_rewrite_as_log(self, x1, x2, **kwargs):
102
+ return _logaddexp2(x1, x2)
103
+
104
+ def _eval_evalf(self, *args, **kwargs):
105
+ return self.rewrite(log).evalf(*args, **kwargs)
106
+
107
+ def _eval_simplify(self, *args, **kwargs):
108
+ a, b = (x.simplify(**kwargs).factor() for x in self.args)
109
+ candidate = _logaddexp2(a, b)
110
+ if candidate != _logaddexp2(a, b, evaluate=False):
111
+ return candidate
112
+ else:
113
+ return logaddexp2(a, b)
114
+
115
+
116
+ class amin(Token):
117
+ """ Minimum value along an axis.
118
+
119
+ Helper class for use with e.g. numpy.amin
120
+
121
+
122
+ See Also
123
+ ========
124
+
125
+ https://numpy.org/doc/stable/reference/generated/numpy.amin.html
126
+ """
127
+ __slots__ = _fields = ('array', 'axis')
128
+ defaults = {'axis': none}
129
+ _construct_axis = staticmethod(sympify)
130
+
131
+
132
+ class amax(Token):
133
+ """ Maximum value along an axis.
134
+
135
+ Helper class for use with e.g. numpy.amax
136
+
137
+
138
+ See Also
139
+ ========
140
+
141
+ https://numpy.org/doc/stable/reference/generated/numpy.amax.html
142
+ """
143
+ __slots__ = _fields = ('array', 'axis')
144
+ defaults = {'axis': none}
145
+ _construct_axis = staticmethod(sympify)
146
+
147
+
148
+ class maximum(Function):
149
+ """ Element-wise maximum of array elements.
150
+
151
+ Helper class for use with e.g. numpy.maximum
152
+
153
+
154
+ See Also
155
+ ========
156
+
157
+ https://numpy.org/doc/stable/reference/generated/numpy.maximum.html
158
+ """
159
+
160
+ def _eval_rewrite_as_Max(self, *args):
161
+ return Max(*self.args)
162
+
163
+
164
+ class minimum(Function):
165
+ """ Element-wise minimum of array elements.
166
+
167
+ Helper class for use with e.g. numpy.minimum
168
+
169
+
170
+ See Also
171
+ ========
172
+
173
+ https://numpy.org/doc/stable/reference/generated/numpy.minimum.html
174
+ """
175
+
176
+ def _eval_rewrite_as_Min(self, *args):
177
+ return Min(*self.args)
.venv/lib/python3.13/site-packages/sympy/codegen/pynodes.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .abstract_nodes import List as AbstractList
2
+ from .ast import Token
3
+
4
+
5
+ class List(AbstractList):
6
+ pass
7
+
8
+
9
+ class NumExprEvaluate(Token):
10
+ """represents a call to :class:`numexpr`s :func:`evaluate`"""
11
+ __slots__ = _fields = ('expr',)
.venv/lib/python3.13/site-packages/sympy/codegen/pyutils.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sympy.printing.pycode import PythonCodePrinter
2
+
3
+ """ This module collects utilities for rendering Python code. """
4
+
5
+
6
+ def render_as_module(content, standard='python3'):
7
+ """Renders Python code as a module (with the required imports).
8
+
9
+ Parameters
10
+ ==========
11
+
12
+ standard :
13
+ See the parameter ``standard`` in
14
+ :meth:`sympy.printing.pycode.pycode`
15
+ """
16
+
17
+ printer = PythonCodePrinter({'standard':standard})
18
+ pystr = printer.doprint(content)
19
+ if printer._settings['fully_qualified_modules']:
20
+ module_imports_str = '\n'.join('import %s' % k for k in printer.module_imports)
21
+ else:
22
+ module_imports_str = '\n'.join(['from %s import %s' % (k, ', '.join(v)) for
23
+ k, v in printer.module_imports.items()])
24
+ return module_imports_str + '\n\n' + pystr
.venv/lib/python3.13/site-packages/sympy/codegen/rewriting.py ADDED
@@ -0,0 +1,357 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Classes and functions useful for rewriting expressions for optimized code
3
+ generation. Some languages (or standards thereof), e.g. C99, offer specialized
4
+ math functions for better performance and/or precision.
5
+
6
+ Using the ``optimize`` function in this module, together with a collection of
7
+ rules (represented as instances of ``Optimization``), one can rewrite the
8
+ expressions for this purpose::
9
+
10
+ >>> from sympy import Symbol, exp, log
11
+ >>> from sympy.codegen.rewriting import optimize, optims_c99
12
+ >>> x = Symbol('x')
13
+ >>> optimize(3*exp(2*x) - 3, optims_c99)
14
+ 3*expm1(2*x)
15
+ >>> optimize(exp(2*x) - 1 - exp(-33), optims_c99)
16
+ expm1(2*x) - exp(-33)
17
+ >>> optimize(log(3*x + 3), optims_c99)
18
+ log1p(x) + log(3)
19
+ >>> optimize(log(2*x + 3), optims_c99)
20
+ log(2*x + 3)
21
+
22
+ The ``optims_c99`` imported above is tuple containing the following instances
23
+ (which may be imported from ``sympy.codegen.rewriting``):
24
+
25
+ - ``expm1_opt``
26
+ - ``log1p_opt``
27
+ - ``exp2_opt``
28
+ - ``log2_opt``
29
+ - ``log2const_opt``
30
+
31
+
32
+ """
33
+ from sympy.core.function import expand_log
34
+ from sympy.core.singleton import S
35
+ from sympy.core.symbol import Wild
36
+ from sympy.functions.elementary.complexes import sign
37
+ from sympy.functions.elementary.exponential import (exp, log)
38
+ from sympy.functions.elementary.miscellaneous import (Max, Min)
39
+ from sympy.functions.elementary.trigonometric import (cos, sin, sinc)
40
+ from sympy.assumptions import Q, ask
41
+ from sympy.codegen.cfunctions import log1p, log2, exp2, expm1
42
+ from sympy.codegen.matrix_nodes import MatrixSolve
43
+ from sympy.core.expr import UnevaluatedExpr
44
+ from sympy.core.power import Pow
45
+ from sympy.codegen.numpy_nodes import logaddexp, logaddexp2
46
+ from sympy.codegen.scipy_nodes import cosm1, powm1
47
+ from sympy.core.mul import Mul
48
+ from sympy.matrices.expressions.matexpr import MatrixSymbol
49
+ from sympy.utilities.iterables import sift
50
+
51
+
52
+ class Optimization:
53
+ """ Abstract base class for rewriting optimization.
54
+
55
+ Subclasses should implement ``__call__`` taking an expression
56
+ as argument.
57
+
58
+ Parameters
59
+ ==========
60
+ cost_function : callable returning number
61
+ priority : number
62
+
63
+ """
64
+ def __init__(self, cost_function=None, priority=1):
65
+ self.cost_function = cost_function
66
+ self.priority=priority
67
+
68
+ def cheapest(self, *args):
69
+ return min(args, key=self.cost_function)
70
+
71
+
72
+ class ReplaceOptim(Optimization):
73
+ """ Rewriting optimization calling replace on expressions.
74
+
75
+ Explanation
76
+ ===========
77
+
78
+ The instance can be used as a function on expressions for which
79
+ it will apply the ``replace`` method (see
80
+ :meth:`sympy.core.basic.Basic.replace`).
81
+
82
+ Parameters
83
+ ==========
84
+
85
+ query :
86
+ First argument passed to replace.
87
+ value :
88
+ Second argument passed to replace.
89
+
90
+ Examples
91
+ ========
92
+
93
+ >>> from sympy import Symbol
94
+ >>> from sympy.codegen.rewriting import ReplaceOptim
95
+ >>> from sympy.codegen.cfunctions import exp2
96
+ >>> x = Symbol('x')
97
+ >>> exp2_opt = ReplaceOptim(lambda p: p.is_Pow and p.base == 2,
98
+ ... lambda p: exp2(p.exp))
99
+ >>> exp2_opt(2**x)
100
+ exp2(x)
101
+
102
+ """
103
+
104
+ def __init__(self, query, value, **kwargs):
105
+ super().__init__(**kwargs)
106
+ self.query = query
107
+ self.value = value
108
+
109
+ def __call__(self, expr):
110
+ return expr.replace(self.query, self.value)
111
+
112
+
113
+ def optimize(expr, optimizations):
114
+ """ Apply optimizations to an expression.
115
+
116
+ Parameters
117
+ ==========
118
+
119
+ expr : expression
120
+ optimizations : iterable of ``Optimization`` instances
121
+ The optimizations will be sorted with respect to ``priority`` (highest first).
122
+
123
+ Examples
124
+ ========
125
+
126
+ >>> from sympy import log, Symbol
127
+ >>> from sympy.codegen.rewriting import optims_c99, optimize
128
+ >>> x = Symbol('x')
129
+ >>> optimize(log(x+3)/log(2) + log(x**2 + 1), optims_c99)
130
+ log1p(x**2) + log2(x + 3)
131
+
132
+ """
133
+
134
+ for optim in sorted(optimizations, key=lambda opt: opt.priority, reverse=True):
135
+ new_expr = optim(expr)
136
+ if optim.cost_function is None:
137
+ expr = new_expr
138
+ else:
139
+ expr = optim.cheapest(expr, new_expr)
140
+ return expr
141
+
142
+
143
+ exp2_opt = ReplaceOptim(
144
+ lambda p: p.is_Pow and p.base == 2,
145
+ lambda p: exp2(p.exp)
146
+ )
147
+
148
+
149
+ _d = Wild('d', properties=[lambda x: x.is_Dummy])
150
+ _u = Wild('u', properties=[lambda x: not x.is_number and not x.is_Add])
151
+ _v = Wild('v')
152
+ _w = Wild('w')
153
+ _n = Wild('n', properties=[lambda x: x.is_number])
154
+
155
+ sinc_opt1 = ReplaceOptim(
156
+ sin(_w)/_w, sinc(_w)
157
+ )
158
+ sinc_opt2 = ReplaceOptim(
159
+ sin(_n*_w)/_w, _n*sinc(_n*_w)
160
+ )
161
+ sinc_opts = (sinc_opt1, sinc_opt2)
162
+
163
+ log2_opt = ReplaceOptim(_v*log(_w)/log(2), _v*log2(_w), cost_function=lambda expr: expr.count(
164
+ lambda e: ( # division & eval of transcendentals are expensive floating point operations...
165
+ e.is_Pow and e.exp.is_negative # division
166
+ or (isinstance(e, (log, log2)) and not e.args[0].is_number)) # transcendental
167
+ )
168
+ )
169
+
170
+ log2const_opt = ReplaceOptim(log(2)*log2(_w), log(_w))
171
+
172
+ logsumexp_2terms_opt = ReplaceOptim(
173
+ lambda l: (isinstance(l, log)
174
+ and l.args[0].is_Add
175
+ and len(l.args[0].args) == 2
176
+ and all(isinstance(t, exp) for t in l.args[0].args)),
177
+ lambda l: (
178
+ Max(*[e.args[0] for e in l.args[0].args]) +
179
+ log1p(exp(Min(*[e.args[0] for e in l.args[0].args])))
180
+ )
181
+ )
182
+
183
+
184
+ class FuncMinusOneOptim(ReplaceOptim):
185
+ """Specialization of ReplaceOptim for functions evaluating "f(x) - 1".
186
+
187
+ Explanation
188
+ ===========
189
+
190
+ Numerical functions which go toward one as x go toward zero is often best
191
+ implemented by a dedicated function in order to avoid catastrophic
192
+ cancellation. One such example is ``expm1(x)`` in the C standard library
193
+ which evaluates ``exp(x) - 1``. Such functions preserves many more
194
+ significant digits when its argument is much smaller than one, compared
195
+ to subtracting one afterwards.
196
+
197
+ Parameters
198
+ ==========
199
+
200
+ func :
201
+ The function which is subtracted by one.
202
+ func_m_1 :
203
+ The specialized function evaluating ``func(x) - 1``.
204
+ opportunistic : bool
205
+ When ``True``, apply the transformation as long as the magnitude of the
206
+ remaining number terms decreases. When ``False``, only apply the
207
+ transformation if it completely eliminates the number term.
208
+
209
+ Examples
210
+ ========
211
+
212
+ >>> from sympy import symbols, exp
213
+ >>> from sympy.codegen.rewriting import FuncMinusOneOptim
214
+ >>> from sympy.codegen.cfunctions import expm1
215
+ >>> x, y = symbols('x y')
216
+ >>> expm1_opt = FuncMinusOneOptim(exp, expm1)
217
+ >>> expm1_opt(exp(x) + 2*exp(5*y) - 3)
218
+ expm1(x) + 2*expm1(5*y)
219
+
220
+
221
+ """
222
+
223
+ def __init__(self, func, func_m_1, opportunistic=True):
224
+ weight = 10 # <-- this is an arbitrary number (heuristic)
225
+ super().__init__(lambda e: e.is_Add, self.replace_in_Add,
226
+ cost_function=lambda expr: expr.count_ops() - weight*expr.count(func_m_1))
227
+ self.func = func
228
+ self.func_m_1 = func_m_1
229
+ self.opportunistic = opportunistic
230
+
231
+ def _group_Add_terms(self, add):
232
+ numbers, non_num = sift(add.args, lambda arg: arg.is_number, binary=True)
233
+ numsum = sum(numbers)
234
+ terms_with_func, other = sift(non_num, lambda arg: arg.has(self.func), binary=True)
235
+ return numsum, terms_with_func, other
236
+
237
+ def replace_in_Add(self, e):
238
+ """ passed as second argument to Basic.replace(...) """
239
+ numsum, terms_with_func, other_non_num_terms = self._group_Add_terms(e)
240
+ if numsum == 0:
241
+ return e
242
+ substituted, untouched = [], []
243
+ for with_func in terms_with_func:
244
+ if with_func.is_Mul:
245
+ func, coeff = sift(with_func.args, lambda arg: arg.func == self.func, binary=True)
246
+ if len(func) == 1 and len(coeff) == 1:
247
+ func, coeff = func[0], coeff[0]
248
+ else:
249
+ coeff = None
250
+ elif with_func.func == self.func:
251
+ func, coeff = with_func, S.One
252
+ else:
253
+ coeff = None
254
+
255
+ if coeff is not None and coeff.is_number and sign(coeff) == -sign(numsum):
256
+ if self.opportunistic:
257
+ do_substitute = abs(coeff+numsum) < abs(numsum)
258
+ else:
259
+ do_substitute = coeff+numsum == 0
260
+
261
+ if do_substitute: # advantageous substitution
262
+ numsum += coeff
263
+ substituted.append(coeff*self.func_m_1(*func.args))
264
+ continue
265
+ untouched.append(with_func)
266
+
267
+ return e.func(numsum, *substituted, *untouched, *other_non_num_terms)
268
+
269
+ def __call__(self, expr):
270
+ alt1 = super().__call__(expr)
271
+ alt2 = super().__call__(expr.factor())
272
+ return self.cheapest(alt1, alt2)
273
+
274
+
275
+ expm1_opt = FuncMinusOneOptim(exp, expm1)
276
+ cosm1_opt = FuncMinusOneOptim(cos, cosm1)
277
+ powm1_opt = FuncMinusOneOptim(Pow, powm1)
278
+
279
+ log1p_opt = ReplaceOptim(
280
+ lambda e: isinstance(e, log),
281
+ lambda l: expand_log(l.replace(
282
+ log, lambda arg: log(arg.factor())
283
+ )).replace(log(_u+1), log1p(_u))
284
+ )
285
+
286
+ def create_expand_pow_optimization(limit, *, base_req=lambda b: b.is_symbol):
287
+ """ Creates an instance of :class:`ReplaceOptim` for expanding ``Pow``.
288
+
289
+ Explanation
290
+ ===========
291
+
292
+ The requirements for expansions are that the base needs to be a symbol
293
+ and the exponent needs to be an Integer (and be less than or equal to
294
+ ``limit``).
295
+
296
+ Parameters
297
+ ==========
298
+
299
+ limit : int
300
+ The highest power which is expanded into multiplication.
301
+ base_req : function returning bool
302
+ Requirement on base for expansion to happen, default is to return
303
+ the ``is_symbol`` attribute of the base.
304
+
305
+ Examples
306
+ ========
307
+
308
+ >>> from sympy import Symbol, sin
309
+ >>> from sympy.codegen.rewriting import create_expand_pow_optimization
310
+ >>> x = Symbol('x')
311
+ >>> expand_opt = create_expand_pow_optimization(3)
312
+ >>> expand_opt(x**5 + x**3)
313
+ x**5 + x*x*x
314
+ >>> expand_opt(x**5 + x**3 + sin(x)**3)
315
+ x**5 + sin(x)**3 + x*x*x
316
+ >>> opt2 = create_expand_pow_optimization(3, base_req=lambda b: not b.is_Function)
317
+ >>> opt2((x+1)**2 + sin(x)**2)
318
+ sin(x)**2 + (x + 1)*(x + 1)
319
+
320
+ """
321
+ return ReplaceOptim(
322
+ lambda e: e.is_Pow and base_req(e.base) and e.exp.is_Integer and abs(e.exp) <= limit,
323
+ lambda p: (
324
+ UnevaluatedExpr(Mul(*([p.base]*+p.exp), evaluate=False)) if p.exp > 0 else
325
+ 1/UnevaluatedExpr(Mul(*([p.base]*-p.exp), evaluate=False))
326
+ ))
327
+
328
+ # Optimization procedures for turning A**(-1) * x into MatrixSolve(A, x)
329
+ def _matinv_predicate(expr):
330
+ # TODO: We should be able to support more than 2 elements
331
+ if expr.is_MatMul and len(expr.args) == 2:
332
+ left, right = expr.args
333
+ if left.is_Inverse and right.shape[1] == 1:
334
+ inv_arg = left.arg
335
+ if isinstance(inv_arg, MatrixSymbol):
336
+ return bool(ask(Q.fullrank(left.arg)))
337
+
338
+ return False
339
+
340
+ def _matinv_transform(expr):
341
+ left, right = expr.args
342
+ inv_arg = left.arg
343
+ return MatrixSolve(inv_arg, right)
344
+
345
+
346
+ matinv_opt = ReplaceOptim(_matinv_predicate, _matinv_transform)
347
+
348
+
349
+ logaddexp_opt = ReplaceOptim(log(exp(_v)+exp(_w)), logaddexp(_v, _w))
350
+ logaddexp2_opt = ReplaceOptim(log(Pow(2, _v)+Pow(2, _w)), logaddexp2(_v, _w)*log(2))
351
+
352
+ # Collections of optimizations:
353
+ optims_c99 = (expm1_opt, log1p_opt, exp2_opt, log2_opt, log2const_opt)
354
+
355
+ optims_numpy = optims_c99 + (logaddexp_opt, logaddexp2_opt,) + sinc_opts
356
+
357
+ optims_scipy = (cosm1_opt, powm1_opt)
.venv/lib/python3.13/site-packages/sympy/codegen/scipy_nodes.py ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sympy.core.function import Add, ArgumentIndexError, Function
2
+ from sympy.core.power import Pow
3
+ from sympy.core.singleton import S
4
+ from sympy.functions.elementary.exponential import log
5
+ from sympy.functions.elementary.trigonometric import cos, sin
6
+
7
+
8
+ def _cosm1(x, *, evaluate=True):
9
+ return Add(cos(x, evaluate=evaluate), -S.One, evaluate=evaluate)
10
+
11
+
12
+ class cosm1(Function):
13
+ """ Minus one plus cosine of x, i.e. cos(x) - 1. For use when x is close to zero.
14
+
15
+ Helper class for use with e.g. scipy.special.cosm1
16
+ See: https://docs.scipy.org/doc/scipy/reference/generated/scipy.special.cosm1.html
17
+ """
18
+ nargs = 1
19
+
20
+ def fdiff(self, argindex=1):
21
+ """
22
+ Returns the first derivative of this function.
23
+ """
24
+ if argindex == 1:
25
+ return -sin(*self.args)
26
+ else:
27
+ raise ArgumentIndexError(self, argindex)
28
+
29
+ def _eval_rewrite_as_cos(self, x, **kwargs):
30
+ return _cosm1(x)
31
+
32
+ def _eval_evalf(self, *args, **kwargs):
33
+ return self.rewrite(cos).evalf(*args, **kwargs)
34
+
35
+ def _eval_simplify(self, **kwargs):
36
+ x, = self.args
37
+ candidate = _cosm1(x.simplify(**kwargs))
38
+ if candidate != _cosm1(x, evaluate=False):
39
+ return candidate
40
+ else:
41
+ return cosm1(x)
42
+
43
+
44
+ def _powm1(x, y, *, evaluate=True):
45
+ return Add(Pow(x, y, evaluate=evaluate), -S.One, evaluate=evaluate)
46
+
47
+
48
+ class powm1(Function):
49
+ """ Minus one plus x to the power of y, i.e. x**y - 1. For use when x is close to one or y is close to zero.
50
+
51
+ Helper class for use with e.g. scipy.special.powm1
52
+ See: https://docs.scipy.org/doc/scipy/reference/generated/scipy.special.powm1.html
53
+ """
54
+ nargs = 2
55
+
56
+ def fdiff(self, argindex=1):
57
+ """
58
+ Returns the first derivative of this function.
59
+ """
60
+ if argindex == 1:
61
+ return Pow(self.args[0], self.args[1])*self.args[1]/self.args[0]
62
+ elif argindex == 2:
63
+ return log(self.args[0])*Pow(*self.args)
64
+ else:
65
+ raise ArgumentIndexError(self, argindex)
66
+
67
+ def _eval_rewrite_as_Pow(self, x, y, **kwargs):
68
+ return _powm1(x, y)
69
+
70
+ def _eval_evalf(self, *args, **kwargs):
71
+ return self.rewrite(Pow).evalf(*args, **kwargs)
72
+
73
+ def _eval_simplify(self, **kwargs):
74
+ x, y = self.args
75
+ candidate = _powm1(x.simplify(**kwargs), y.simplify(**kwargs))
76
+ if candidate != _powm1(x, y, evaluate=False):
77
+ return candidate
78
+ else:
79
+ return powm1(x, y)
.venv/lib/python3.13/site-packages/sympy/combinatorics/__init__.py ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sympy.combinatorics.permutations import Permutation, Cycle
2
+ from sympy.combinatorics.prufer import Prufer
3
+ from sympy.combinatorics.generators import cyclic, alternating, symmetric, dihedral
4
+ from sympy.combinatorics.subsets import Subset
5
+ from sympy.combinatorics.partitions import (Partition, IntegerPartition,
6
+ RGS_rank, RGS_unrank, RGS_enum)
7
+ from sympy.combinatorics.polyhedron import (Polyhedron, tetrahedron, cube,
8
+ octahedron, dodecahedron, icosahedron)
9
+ from sympy.combinatorics.perm_groups import PermutationGroup, Coset, SymmetricPermutationGroup
10
+ from sympy.combinatorics.group_constructs import DirectProduct
11
+ from sympy.combinatorics.graycode import GrayCode
12
+ from sympy.combinatorics.named_groups import (SymmetricGroup, DihedralGroup,
13
+ CyclicGroup, AlternatingGroup, AbelianGroup, RubikGroup)
14
+ from sympy.combinatorics.pc_groups import PolycyclicGroup, Collector
15
+ from sympy.combinatorics.free_groups import free_group
16
+
17
+ __all__ = [
18
+ 'Permutation', 'Cycle',
19
+
20
+ 'Prufer',
21
+
22
+ 'cyclic', 'alternating', 'symmetric', 'dihedral',
23
+
24
+ 'Subset',
25
+
26
+ 'Partition', 'IntegerPartition', 'RGS_rank', 'RGS_unrank', 'RGS_enum',
27
+
28
+ 'Polyhedron', 'tetrahedron', 'cube', 'octahedron', 'dodecahedron',
29
+ 'icosahedron',
30
+
31
+ 'PermutationGroup', 'Coset', 'SymmetricPermutationGroup',
32
+
33
+ 'DirectProduct',
34
+
35
+ 'GrayCode',
36
+
37
+ 'SymmetricGroup', 'DihedralGroup', 'CyclicGroup', 'AlternatingGroup',
38
+ 'AbelianGroup', 'RubikGroup',
39
+
40
+ 'PolycyclicGroup', 'Collector',
41
+
42
+ 'free_group',
43
+ ]
.venv/lib/python3.13/site-packages/sympy/combinatorics/coset_table.py ADDED
@@ -0,0 +1,1259 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sympy.combinatorics.free_groups import free_group
2
+ from sympy.printing.defaults import DefaultPrinting
3
+
4
+ from itertools import chain, product
5
+ from bisect import bisect_left
6
+
7
+
8
+ ###############################################################################
9
+ # COSET TABLE #
10
+ ###############################################################################
11
+
12
+ class CosetTable(DefaultPrinting):
13
+ # coset_table: Mathematically a coset table
14
+ # represented using a list of lists
15
+ # alpha: Mathematically a coset (precisely, a live coset)
16
+ # represented by an integer between i with 1 <= i <= n
17
+ # alpha in c
18
+ # x: Mathematically an element of "A" (set of generators and
19
+ # their inverses), represented using "FpGroupElement"
20
+ # fp_grp: Finitely Presented Group with < X|R > as presentation.
21
+ # H: subgroup of fp_grp.
22
+ # NOTE: We start with H as being only a list of words in generators
23
+ # of "fp_grp". Since `.subgroup` method has not been implemented.
24
+
25
+ r"""
26
+
27
+ Properties
28
+ ==========
29
+
30
+ [1] `0 \in \Omega` and `\tau(1) = \epsilon`
31
+ [2] `\alpha^x = \beta \Leftrightarrow \beta^{x^{-1}} = \alpha`
32
+ [3] If `\alpha^x = \beta`, then `H \tau(\alpha)x = H \tau(\beta)`
33
+ [4] `\forall \alpha \in \Omega, 1^{\tau(\alpha)} = \alpha`
34
+
35
+ References
36
+ ==========
37
+
38
+ .. [1] Holt, D., Eick, B., O'Brien, E.
39
+ "Handbook of Computational Group Theory"
40
+
41
+ .. [2] John J. Cannon; Lucien A. Dimino; George Havas; Jane M. Watson
42
+ Mathematics of Computation, Vol. 27, No. 123. (Jul., 1973), pp. 463-490.
43
+ "Implementation and Analysis of the Todd-Coxeter Algorithm"
44
+
45
+ """
46
+ # default limit for the number of cosets allowed in a
47
+ # coset enumeration.
48
+ coset_table_max_limit = 4096000
49
+ # limit for the current instance
50
+ coset_table_limit = None
51
+ # maximum size of deduction stack above or equal to
52
+ # which it is emptied
53
+ max_stack_size = 100
54
+
55
+ def __init__(self, fp_grp, subgroup, max_cosets=None):
56
+ if not max_cosets:
57
+ max_cosets = CosetTable.coset_table_max_limit
58
+ self.fp_group = fp_grp
59
+ self.subgroup = subgroup
60
+ self.coset_table_limit = max_cosets
61
+ # "p" is setup independent of Omega and n
62
+ self.p = [0]
63
+ # a list of the form `[gen_1, gen_1^{-1}, ... , gen_k, gen_k^{-1}]`
64
+ self.A = list(chain.from_iterable((gen, gen**-1) \
65
+ for gen in self.fp_group.generators))
66
+ #P[alpha, x] Only defined when alpha^x is defined.
67
+ self.P = [[None]*len(self.A)]
68
+ # the mathematical coset table which is a list of lists
69
+ self.table = [[None]*len(self.A)]
70
+ self.A_dict = {x: self.A.index(x) for x in self.A}
71
+ self.A_dict_inv = {}
72
+ for x, index in self.A_dict.items():
73
+ if index % 2 == 0:
74
+ self.A_dict_inv[x] = self.A_dict[x] + 1
75
+ else:
76
+ self.A_dict_inv[x] = self.A_dict[x] - 1
77
+ # used in the coset-table based method of coset enumeration. Each of
78
+ # the element is called a "deduction" which is the form (alpha, x) whenever
79
+ # a value is assigned to alpha^x during a definition or "deduction process"
80
+ self.deduction_stack = []
81
+ # Attributes for modified methods.
82
+ H = self.subgroup
83
+ self._grp = free_group(', ' .join(["a_%d" % i for i in range(len(H))]))[0]
84
+ self.P = [[None]*len(self.A)]
85
+ self.p_p = {}
86
+
87
+ @property
88
+ def omega(self):
89
+ """Set of live cosets. """
90
+ return [coset for coset in range(len(self.p)) if self.p[coset] == coset]
91
+
92
+ def copy(self):
93
+ """
94
+ Return a shallow copy of Coset Table instance ``self``.
95
+
96
+ """
97
+ self_copy = self.__class__(self.fp_group, self.subgroup)
98
+ self_copy.table = [list(perm_rep) for perm_rep in self.table]
99
+ self_copy.p = list(self.p)
100
+ self_copy.deduction_stack = list(self.deduction_stack)
101
+ return self_copy
102
+
103
+ def __str__(self):
104
+ return "Coset Table on %s with %s as subgroup generators" \
105
+ % (self.fp_group, self.subgroup)
106
+
107
+ __repr__ = __str__
108
+
109
+ @property
110
+ def n(self):
111
+ """The number `n` represents the length of the sublist containing the
112
+ live cosets.
113
+
114
+ """
115
+ if not self.table:
116
+ return 0
117
+ return max(self.omega) + 1
118
+
119
+ # Pg. 152 [1]
120
+ def is_complete(self):
121
+ r"""
122
+ The coset table is called complete if it has no undefined entries
123
+ on the live cosets; that is, `\alpha^x` is defined for all
124
+ `\alpha \in \Omega` and `x \in A`.
125
+
126
+ """
127
+ return not any(None in self.table[coset] for coset in self.omega)
128
+
129
+ # Pg. 153 [1]
130
+ def define(self, alpha, x, modified=False):
131
+ r"""
132
+ This routine is used in the relator-based strategy of Todd-Coxeter
133
+ algorithm if some `\alpha^x` is undefined. We check whether there is
134
+ space available for defining a new coset. If there is enough space
135
+ then we remedy this by adjoining a new coset `\beta` to `\Omega`
136
+ (i.e to set of live cosets) and put that equal to `\alpha^x`, then
137
+ make an assignment satisfying Property[1]. If there is not enough space
138
+ then we halt the Coset Table creation. The maximum amount of space that
139
+ can be used by Coset Table can be manipulated using the class variable
140
+ ``CosetTable.coset_table_max_limit``.
141
+
142
+ See Also
143
+ ========
144
+
145
+ define_c
146
+
147
+ """
148
+ A = self.A
149
+ table = self.table
150
+ len_table = len(table)
151
+ if len_table >= self.coset_table_limit:
152
+ # abort the further generation of cosets
153
+ raise ValueError("the coset enumeration has defined more than "
154
+ "%s cosets. Try with a greater value max number of cosets "
155
+ % self.coset_table_limit)
156
+ table.append([None]*len(A))
157
+ self.P.append([None]*len(self.A))
158
+ # beta is the new coset generated
159
+ beta = len_table
160
+ self.p.append(beta)
161
+ table[alpha][self.A_dict[x]] = beta
162
+ table[beta][self.A_dict_inv[x]] = alpha
163
+ # P[alpha][x] = epsilon, P[beta][x**-1] = epsilon
164
+ if modified:
165
+ self.P[alpha][self.A_dict[x]] = self._grp.identity
166
+ self.P[beta][self.A_dict_inv[x]] = self._grp.identity
167
+ self.p_p[beta] = self._grp.identity
168
+
169
+ def define_c(self, alpha, x):
170
+ r"""
171
+ A variation of ``define`` routine, described on Pg. 165 [1], used in
172
+ the coset table-based strategy of Todd-Coxeter algorithm. It differs
173
+ from ``define`` routine in that for each definition it also adds the
174
+ tuple `(\alpha, x)` to the deduction stack.
175
+
176
+ See Also
177
+ ========
178
+
179
+ define
180
+
181
+ """
182
+ A = self.A
183
+ table = self.table
184
+ len_table = len(table)
185
+ if len_table >= self.coset_table_limit:
186
+ # abort the further generation of cosets
187
+ raise ValueError("the coset enumeration has defined more than "
188
+ "%s cosets. Try with a greater value max number of cosets "
189
+ % self.coset_table_limit)
190
+ table.append([None]*len(A))
191
+ # beta is the new coset generated
192
+ beta = len_table
193
+ self.p.append(beta)
194
+ table[alpha][self.A_dict[x]] = beta
195
+ table[beta][self.A_dict_inv[x]] = alpha
196
+ # append to deduction stack
197
+ self.deduction_stack.append((alpha, x))
198
+
199
+ def scan_c(self, alpha, word):
200
+ """
201
+ A variation of ``scan`` routine, described on pg. 165 of [1], which
202
+ puts at tuple, whenever a deduction occurs, to deduction stack.
203
+
204
+ See Also
205
+ ========
206
+
207
+ scan, scan_check, scan_and_fill, scan_and_fill_c
208
+
209
+ """
210
+ # alpha is an integer representing a "coset"
211
+ # since scanning can be in two cases
212
+ # 1. for alpha=0 and w in Y (i.e generating set of H)
213
+ # 2. alpha in Omega (set of live cosets), w in R (relators)
214
+ A_dict = self.A_dict
215
+ A_dict_inv = self.A_dict_inv
216
+ table = self.table
217
+ f = alpha
218
+ i = 0
219
+ r = len(word)
220
+ b = alpha
221
+ j = r - 1
222
+ # list of union of generators and their inverses
223
+ while i <= j and table[f][A_dict[word[i]]] is not None:
224
+ f = table[f][A_dict[word[i]]]
225
+ i += 1
226
+ if i > j:
227
+ if f != b:
228
+ self.coincidence_c(f, b)
229
+ return
230
+ while j >= i and table[b][A_dict_inv[word[j]]] is not None:
231
+ b = table[b][A_dict_inv[word[j]]]
232
+ j -= 1
233
+ if j < i:
234
+ # we have an incorrect completed scan with coincidence f ~ b
235
+ # run the "coincidence" routine
236
+ self.coincidence_c(f, b)
237
+ elif j == i:
238
+ # deduction process
239
+ table[f][A_dict[word[i]]] = b
240
+ table[b][A_dict_inv[word[i]]] = f
241
+ self.deduction_stack.append((f, word[i]))
242
+ # otherwise scan is incomplete and yields no information
243
+
244
+ # alpha, beta coincide, i.e. alpha, beta represent the pair of cosets where
245
+ # coincidence occurs
246
+ def coincidence_c(self, alpha, beta):
247
+ """
248
+ A variation of ``coincidence`` routine used in the coset-table based
249
+ method of coset enumeration. The only difference being on addition of
250
+ a new coset in coset table(i.e new coset introduction), then it is
251
+ appended to ``deduction_stack``.
252
+
253
+ See Also
254
+ ========
255
+
256
+ coincidence
257
+
258
+ """
259
+ A_dict = self.A_dict
260
+ A_dict_inv = self.A_dict_inv
261
+ table = self.table
262
+ # behaves as a queue
263
+ q = []
264
+ self.merge(alpha, beta, q)
265
+ while len(q) > 0:
266
+ gamma = q.pop(0)
267
+ for x in A_dict:
268
+ delta = table[gamma][A_dict[x]]
269
+ if delta is not None:
270
+ table[delta][A_dict_inv[x]] = None
271
+ # only line of difference from ``coincidence`` routine
272
+ self.deduction_stack.append((delta, x**-1))
273
+ mu = self.rep(gamma)
274
+ nu = self.rep(delta)
275
+ if table[mu][A_dict[x]] is not None:
276
+ self.merge(nu, table[mu][A_dict[x]], q)
277
+ elif table[nu][A_dict_inv[x]] is not None:
278
+ self.merge(mu, table[nu][A_dict_inv[x]], q)
279
+ else:
280
+ table[mu][A_dict[x]] = nu
281
+ table[nu][A_dict_inv[x]] = mu
282
+
283
+ def scan(self, alpha, word, y=None, fill=False, modified=False):
284
+ r"""
285
+ ``scan`` performs a scanning process on the input ``word``.
286
+ It first locates the largest prefix ``s`` of ``word`` for which
287
+ `\alpha^s` is defined (i.e is not ``None``), ``s`` may be empty. Let
288
+ ``word=sv``, let ``t`` be the longest suffix of ``v`` for which
289
+ `\alpha^{t^{-1}}` is defined, and let ``v=ut``. Then three
290
+ possibilities are there:
291
+
292
+ 1. If ``t=v``, then we say that the scan completes, and if, in addition
293
+ `\alpha^s = \alpha^{t^{-1}}`, then we say that the scan completes
294
+ correctly.
295
+
296
+ 2. It can also happen that scan does not complete, but `|u|=1`; that
297
+ is, the word ``u`` consists of a single generator `x \in A`. In that
298
+ case, if `\alpha^s = \beta` and `\alpha^{t^{-1}} = \gamma`, then we can
299
+ set `\beta^x = \gamma` and `\gamma^{x^{-1}} = \beta`. These assignments
300
+ are known as deductions and enable the scan to complete correctly.
301
+
302
+ 3. See ``coicidence`` routine for explanation of third condition.
303
+
304
+ Notes
305
+ =====
306
+
307
+ The code for the procedure of scanning `\alpha \in \Omega`
308
+ under `w \in A*` is defined on pg. 155 [1]
309
+
310
+ See Also
311
+ ========
312
+
313
+ scan_c, scan_check, scan_and_fill, scan_and_fill_c
314
+
315
+ Scan and Fill
316
+ =============
317
+
318
+ Performed when the default argument fill=True.
319
+
320
+ Modified Scan
321
+ =============
322
+
323
+ Performed when the default argument modified=True
324
+
325
+ """
326
+ # alpha is an integer representing a "coset"
327
+ # since scanning can be in two cases
328
+ # 1. for alpha=0 and w in Y (i.e generating set of H)
329
+ # 2. alpha in Omega (set of live cosets), w in R (relators)
330
+ A_dict = self.A_dict
331
+ A_dict_inv = self.A_dict_inv
332
+ table = self.table
333
+ f = alpha
334
+ i = 0
335
+ r = len(word)
336
+ b = alpha
337
+ j = r - 1
338
+ b_p = y
339
+ if modified:
340
+ f_p = self._grp.identity
341
+ flag = 0
342
+ while fill or flag == 0:
343
+ flag = 1
344
+ while i <= j and table[f][A_dict[word[i]]] is not None:
345
+ if modified:
346
+ f_p = f_p*self.P[f][A_dict[word[i]]]
347
+ f = table[f][A_dict[word[i]]]
348
+ i += 1
349
+ if i > j:
350
+ if f != b:
351
+ if modified:
352
+ self.modified_coincidence(f, b, f_p**-1*y)
353
+ else:
354
+ self.coincidence(f, b)
355
+ return
356
+ while j >= i and table[b][A_dict_inv[word[j]]] is not None:
357
+ if modified:
358
+ b_p = b_p*self.P[b][self.A_dict_inv[word[j]]]
359
+ b = table[b][A_dict_inv[word[j]]]
360
+ j -= 1
361
+ if j < i:
362
+ # we have an incorrect completed scan with coincidence f ~ b
363
+ # run the "coincidence" routine
364
+ if modified:
365
+ self.modified_coincidence(f, b, f_p**-1*b_p)
366
+ else:
367
+ self.coincidence(f, b)
368
+ elif j == i:
369
+ # deduction process
370
+ table[f][A_dict[word[i]]] = b
371
+ table[b][A_dict_inv[word[i]]] = f
372
+ if modified:
373
+ self.P[f][self.A_dict[word[i]]] = f_p**-1*b_p
374
+ self.P[b][self.A_dict_inv[word[i]]] = b_p**-1*f_p
375
+ return
376
+ elif fill:
377
+ self.define(f, word[i], modified=modified)
378
+ # otherwise scan is incomplete and yields no information
379
+
380
+ # used in the low-index subgroups algorithm
381
+ def scan_check(self, alpha, word):
382
+ r"""
383
+ Another version of ``scan`` routine, described on, it checks whether
384
+ `\alpha` scans correctly under `word`, it is a straightforward
385
+ modification of ``scan``. ``scan_check`` returns ``False`` (rather than
386
+ calling ``coincidence``) if the scan completes incorrectly; otherwise
387
+ it returns ``True``.
388
+
389
+ See Also
390
+ ========
391
+
392
+ scan, scan_c, scan_and_fill, scan_and_fill_c
393
+
394
+ """
395
+ # alpha is an integer representing a "coset"
396
+ # since scanning can be in two cases
397
+ # 1. for alpha=0 and w in Y (i.e generating set of H)
398
+ # 2. alpha in Omega (set of live cosets), w in R (relators)
399
+ A_dict = self.A_dict
400
+ A_dict_inv = self.A_dict_inv
401
+ table = self.table
402
+ f = alpha
403
+ i = 0
404
+ r = len(word)
405
+ b = alpha
406
+ j = r - 1
407
+ while i <= j and table[f][A_dict[word[i]]] is not None:
408
+ f = table[f][A_dict[word[i]]]
409
+ i += 1
410
+ if i > j:
411
+ return f == b
412
+ while j >= i and table[b][A_dict_inv[word[j]]] is not None:
413
+ b = table[b][A_dict_inv[word[j]]]
414
+ j -= 1
415
+ if j < i:
416
+ # we have an incorrect completed scan with coincidence f ~ b
417
+ # return False, instead of calling coincidence routine
418
+ return False
419
+ elif j == i:
420
+ # deduction process
421
+ table[f][A_dict[word[i]]] = b
422
+ table[b][A_dict_inv[word[i]]] = f
423
+ return True
424
+
425
+ def merge(self, k, lamda, q, w=None, modified=False):
426
+ """
427
+ Merge two classes with representatives ``k`` and ``lamda``, described
428
+ on Pg. 157 [1] (for pseudocode), start by putting ``p[k] = lamda``.
429
+ It is more efficient to choose the new representative from the larger
430
+ of the two classes being merged, i.e larger among ``k`` and ``lamda``.
431
+ procedure ``merge`` performs the merging operation, adds the deleted
432
+ class representative to the queue ``q``.
433
+
434
+ Parameters
435
+ ==========
436
+
437
+ 'k', 'lamda' being the two class representatives to be merged.
438
+
439
+ Notes
440
+ =====
441
+
442
+ Pg. 86-87 [1] contains a description of this method.
443
+
444
+ See Also
445
+ ========
446
+
447
+ coincidence, rep
448
+
449
+ """
450
+ p = self.p
451
+ rep = self.rep
452
+ phi = rep(k, modified=modified)
453
+ psi = rep(lamda, modified=modified)
454
+ if phi != psi:
455
+ mu = min(phi, psi)
456
+ v = max(phi, psi)
457
+ p[v] = mu
458
+ if modified:
459
+ if v == phi:
460
+ self.p_p[phi] = self.p_p[k]**-1*w*self.p_p[lamda]
461
+ else:
462
+ self.p_p[psi] = self.p_p[lamda]**-1*w**-1*self.p_p[k]
463
+ q.append(v)
464
+
465
+ def rep(self, k, modified=False):
466
+ r"""
467
+ Parameters
468
+ ==========
469
+
470
+ `k \in [0 \ldots n-1]`, as for ``self`` only array ``p`` is used
471
+
472
+ Returns
473
+ =======
474
+
475
+ Representative of the class containing ``k``.
476
+
477
+ Returns the representative of `\sim` class containing ``k``, it also
478
+ makes some modification to array ``p`` of ``self`` to ease further
479
+ computations, described on Pg. 157 [1].
480
+
481
+ The information on classes under `\sim` is stored in array `p` of
482
+ ``self`` argument, which will always satisfy the property:
483
+
484
+ `p[\alpha] \sim \alpha` and `p[\alpha]=\alpha \iff \alpha=rep(\alpha)`
485
+ `\forall \in [0 \ldots n-1]`.
486
+
487
+ So, for `\alpha \in [0 \ldots n-1]`, we find `rep(self, \alpha)` by
488
+ continually replacing `\alpha` by `p[\alpha]` until it becomes
489
+ constant (i.e satisfies `p[\alpha] = \alpha`):w
490
+
491
+ To increase the efficiency of later ``rep`` calculations, whenever we
492
+ find `rep(self, \alpha)=\beta`, we set
493
+ `p[\gamma] = \beta \forall \gamma \in p-chain` from `\alpha` to `\beta`
494
+
495
+ Notes
496
+ =====
497
+
498
+ ``rep`` routine is also described on Pg. 85-87 [1] in Atkinson's
499
+ algorithm, this results from the fact that ``coincidence`` routine
500
+ introduces functionality similar to that introduced by the
501
+ ``minimal_block`` routine on Pg. 85-87 [1].
502
+
503
+ See Also
504
+ ========
505
+
506
+ coincidence, merge
507
+
508
+ """
509
+ p = self.p
510
+ lamda = k
511
+ rho = p[lamda]
512
+ if modified:
513
+ s = p[:]
514
+ while rho != lamda:
515
+ if modified:
516
+ s[rho] = lamda
517
+ lamda = rho
518
+ rho = p[lamda]
519
+ if modified:
520
+ rho = s[lamda]
521
+ while rho != k:
522
+ mu = rho
523
+ rho = s[mu]
524
+ p[rho] = lamda
525
+ self.p_p[rho] = self.p_p[rho]*self.p_p[mu]
526
+ else:
527
+ mu = k
528
+ rho = p[mu]
529
+ while rho != lamda:
530
+ p[mu] = lamda
531
+ mu = rho
532
+ rho = p[mu]
533
+ return lamda
534
+
535
+ # alpha, beta coincide, i.e. alpha, beta represent the pair of cosets
536
+ # where coincidence occurs
537
+ def coincidence(self, alpha, beta, w=None, modified=False):
538
+ r"""
539
+ The third situation described in ``scan`` routine is handled by this
540
+ routine, described on Pg. 156-161 [1].
541
+
542
+ The unfortunate situation when the scan completes but not correctly,
543
+ then ``coincidence`` routine is run. i.e when for some `i` with
544
+ `1 \le i \le r+1`, we have `w=st` with `s = x_1 x_2 \dots x_{i-1}`,
545
+ `t = x_i x_{i+1} \dots x_r`, and `\beta = \alpha^s` and
546
+ `\gamma = \alpha^{t-1}` are defined but unequal. This means that
547
+ `\beta` and `\gamma` represent the same coset of `H` in `G`. Described
548
+ on Pg. 156 [1]. ``rep``
549
+
550
+ See Also
551
+ ========
552
+
553
+ scan
554
+
555
+ """
556
+ A_dict = self.A_dict
557
+ A_dict_inv = self.A_dict_inv
558
+ table = self.table
559
+ # behaves as a queue
560
+ q = []
561
+ if modified:
562
+ self.modified_merge(alpha, beta, w, q)
563
+ else:
564
+ self.merge(alpha, beta, q)
565
+ while len(q) > 0:
566
+ gamma = q.pop(0)
567
+ for x in A_dict:
568
+ delta = table[gamma][A_dict[x]]
569
+ if delta is not None:
570
+ table[delta][A_dict_inv[x]] = None
571
+ mu = self.rep(gamma, modified=modified)
572
+ nu = self.rep(delta, modified=modified)
573
+ if table[mu][A_dict[x]] is not None:
574
+ if modified:
575
+ v = self.p_p[delta]**-1*self.P[gamma][self.A_dict[x]]**-1
576
+ v = v*self.p_p[gamma]*self.P[mu][self.A_dict[x]]
577
+ self.modified_merge(nu, table[mu][self.A_dict[x]], v, q)
578
+ else:
579
+ self.merge(nu, table[mu][A_dict[x]], q)
580
+ elif table[nu][A_dict_inv[x]] is not None:
581
+ if modified:
582
+ v = self.p_p[gamma]**-1*self.P[gamma][self.A_dict[x]]
583
+ v = v*self.p_p[delta]*self.P[mu][self.A_dict_inv[x]]
584
+ self.modified_merge(mu, table[nu][self.A_dict_inv[x]], v, q)
585
+ else:
586
+ self.merge(mu, table[nu][A_dict_inv[x]], q)
587
+ else:
588
+ table[mu][A_dict[x]] = nu
589
+ table[nu][A_dict_inv[x]] = mu
590
+ if modified:
591
+ v = self.p_p[gamma]**-1*self.P[gamma][self.A_dict[x]]*self.p_p[delta]
592
+ self.P[mu][self.A_dict[x]] = v
593
+ self.P[nu][self.A_dict_inv[x]] = v**-1
594
+
595
+ # method used in the HLT strategy
596
+ def scan_and_fill(self, alpha, word):
597
+ """
598
+ A modified version of ``scan`` routine used in the relator-based
599
+ method of coset enumeration, described on pg. 162-163 [1], which
600
+ follows the idea that whenever the procedure is called and the scan
601
+ is incomplete then it makes new definitions to enable the scan to
602
+ complete; i.e it fills in the gaps in the scan of the relator or
603
+ subgroup generator.
604
+
605
+ """
606
+ self.scan(alpha, word, fill=True)
607
+
608
+ def scan_and_fill_c(self, alpha, word):
609
+ """
610
+ A modified version of ``scan`` routine, described on Pg. 165 second
611
+ para. [1], with modification similar to that of ``scan_anf_fill`` the
612
+ only difference being it calls the coincidence procedure used in the
613
+ coset-table based method i.e. the routine ``coincidence_c`` is used.
614
+
615
+ See Also
616
+ ========
617
+
618
+ scan, scan_and_fill
619
+
620
+ """
621
+ A_dict = self.A_dict
622
+ A_dict_inv = self.A_dict_inv
623
+ table = self.table
624
+ r = len(word)
625
+ f = alpha
626
+ i = 0
627
+ b = alpha
628
+ j = r - 1
629
+ # loop until it has filled the alpha row in the table.
630
+ while True:
631
+ # do the forward scanning
632
+ while i <= j and table[f][A_dict[word[i]]] is not None:
633
+ f = table[f][A_dict[word[i]]]
634
+ i += 1
635
+ if i > j:
636
+ if f != b:
637
+ self.coincidence_c(f, b)
638
+ return
639
+ # forward scan was incomplete, scan backwards
640
+ while j >= i and table[b][A_dict_inv[word[j]]] is not None:
641
+ b = table[b][A_dict_inv[word[j]]]
642
+ j -= 1
643
+ if j < i:
644
+ self.coincidence_c(f, b)
645
+ elif j == i:
646
+ table[f][A_dict[word[i]]] = b
647
+ table[b][A_dict_inv[word[i]]] = f
648
+ self.deduction_stack.append((f, word[i]))
649
+ else:
650
+ self.define_c(f, word[i])
651
+
652
+ # method used in the HLT strategy
653
+ def look_ahead(self):
654
+ """
655
+ When combined with the HLT method this is known as HLT+Lookahead
656
+ method of coset enumeration, described on pg. 164 [1]. Whenever
657
+ ``define`` aborts due to lack of space available this procedure is
658
+ executed. This routine helps in recovering space resulting from
659
+ "coincidence" of cosets.
660
+
661
+ """
662
+ R = self.fp_group.relators
663
+ p = self.p
664
+ # complete scan all relators under all cosets(obviously live)
665
+ # without making new definitions
666
+ for beta in self.omega:
667
+ for w in R:
668
+ self.scan(beta, w)
669
+ if p[beta] < beta:
670
+ break
671
+
672
+ # Pg. 166
673
+ def process_deductions(self, R_c_x, R_c_x_inv):
674
+ """
675
+ Processes the deductions that have been pushed onto ``deduction_stack``,
676
+ described on Pg. 166 [1] and is used in coset-table based enumeration.
677
+
678
+ See Also
679
+ ========
680
+
681
+ deduction_stack
682
+
683
+ """
684
+ p = self.p
685
+ table = self.table
686
+ while len(self.deduction_stack) > 0:
687
+ if len(self.deduction_stack) >= CosetTable.max_stack_size:
688
+ self.look_ahead()
689
+ del self.deduction_stack[:]
690
+ continue
691
+ else:
692
+ alpha, x = self.deduction_stack.pop()
693
+ if p[alpha] == alpha:
694
+ for w in R_c_x:
695
+ self.scan_c(alpha, w)
696
+ if p[alpha] < alpha:
697
+ break
698
+ beta = table[alpha][self.A_dict[x]]
699
+ if beta is not None and p[beta] == beta:
700
+ for w in R_c_x_inv:
701
+ self.scan_c(beta, w)
702
+ if p[beta] < beta:
703
+ break
704
+
705
+ def process_deductions_check(self, R_c_x, R_c_x_inv):
706
+ """
707
+ A variation of ``process_deductions``, this calls ``scan_check``
708
+ wherever ``process_deductions`` calls ``scan``, described on Pg. [1].
709
+
710
+ See Also
711
+ ========
712
+
713
+ process_deductions
714
+
715
+ """
716
+ table = self.table
717
+ while len(self.deduction_stack) > 0:
718
+ alpha, x = self.deduction_stack.pop()
719
+ if not all(self.scan_check(alpha, w) for w in R_c_x):
720
+ return False
721
+ beta = table[alpha][self.A_dict[x]]
722
+ if beta is not None:
723
+ if not all(self.scan_check(beta, w) for w in R_c_x_inv):
724
+ return False
725
+ return True
726
+
727
+ def switch(self, beta, gamma):
728
+ r"""Switch the elements `\beta, \gamma \in \Omega` of ``self``, used
729
+ by the ``standardize`` procedure, described on Pg. 167 [1].
730
+
731
+ See Also
732
+ ========
733
+
734
+ standardize
735
+
736
+ """
737
+ A = self.A
738
+ A_dict = self.A_dict
739
+ table = self.table
740
+ for x in A:
741
+ z = table[gamma][A_dict[x]]
742
+ table[gamma][A_dict[x]] = table[beta][A_dict[x]]
743
+ table[beta][A_dict[x]] = z
744
+ for alpha in range(len(self.p)):
745
+ if self.p[alpha] == alpha:
746
+ if table[alpha][A_dict[x]] == beta:
747
+ table[alpha][A_dict[x]] = gamma
748
+ elif table[alpha][A_dict[x]] == gamma:
749
+ table[alpha][A_dict[x]] = beta
750
+
751
+ def standardize(self):
752
+ r"""
753
+ A coset table is standardized if when running through the cosets and
754
+ within each coset through the generator images (ignoring generator
755
+ inverses), the cosets appear in order of the integers
756
+ `0, 1, \dots, n`. "Standardize" reorders the elements of `\Omega`
757
+ such that, if we scan the coset table first by elements of `\Omega`
758
+ and then by elements of A, then the cosets occur in ascending order.
759
+ ``standardize()`` is used at the end of an enumeration to permute the
760
+ cosets so that they occur in some sort of standard order.
761
+
762
+ Notes
763
+ =====
764
+
765
+ procedure is described on pg. 167-168 [1], it also makes use of the
766
+ ``switch`` routine to replace by smaller integer value.
767
+
768
+ Examples
769
+ ========
770
+
771
+ >>> from sympy.combinatorics import free_group
772
+ >>> from sympy.combinatorics.fp_groups import FpGroup, coset_enumeration_r
773
+ >>> F, x, y = free_group("x, y")
774
+
775
+ # Example 5.3 from [1]
776
+ >>> f = FpGroup(F, [x**2*y**2, x**3*y**5])
777
+ >>> C = coset_enumeration_r(f, [])
778
+ >>> C.compress()
779
+ >>> C.table
780
+ [[1, 3, 1, 3], [2, 0, 2, 0], [3, 1, 3, 1], [0, 2, 0, 2]]
781
+ >>> C.standardize()
782
+ >>> C.table
783
+ [[1, 2, 1, 2], [3, 0, 3, 0], [0, 3, 0, 3], [2, 1, 2, 1]]
784
+
785
+ """
786
+ A = self.A
787
+ A_dict = self.A_dict
788
+ gamma = 1
789
+ for alpha, x in product(range(self.n), A):
790
+ beta = self.table[alpha][A_dict[x]]
791
+ if beta >= gamma:
792
+ if beta > gamma:
793
+ self.switch(gamma, beta)
794
+ gamma += 1
795
+ if gamma == self.n:
796
+ return
797
+
798
+ # Compression of a Coset Table
799
+ def compress(self):
800
+ """Removes the non-live cosets from the coset table, described on
801
+ pg. 167 [1].
802
+
803
+ """
804
+ gamma = -1
805
+ A = self.A
806
+ A_dict = self.A_dict
807
+ A_dict_inv = self.A_dict_inv
808
+ table = self.table
809
+ chi = tuple([i for i in range(len(self.p)) if self.p[i] != i])
810
+ for alpha in self.omega:
811
+ gamma += 1
812
+ if gamma != alpha:
813
+ # replace alpha by gamma in coset table
814
+ for x in A:
815
+ beta = table[alpha][A_dict[x]]
816
+ table[gamma][A_dict[x]] = beta
817
+ # XXX: The line below uses == rather than = which means
818
+ # that it has no effect. It is not clear though if it is
819
+ # correct simply to delete the line or to change it to
820
+ # use =. Changing it causes some tests to fail.
821
+ #
822
+ # https://github.com/sympy/sympy/issues/27633
823
+ table[beta][A_dict_inv[x]] == gamma # noqa: B015
824
+ # all the cosets in the table are live cosets
825
+ self.p = list(range(gamma + 1))
826
+ # delete the useless columns
827
+ del table[len(self.p):]
828
+ # re-define values
829
+ for row in table:
830
+ for j in range(len(self.A)):
831
+ row[j] -= bisect_left(chi, row[j])
832
+
833
+ def conjugates(self, R):
834
+ R_c = list(chain.from_iterable((rel.cyclic_conjugates(), \
835
+ (rel**-1).cyclic_conjugates()) for rel in R))
836
+ R_set = set()
837
+ for conjugate in R_c:
838
+ R_set = R_set.union(conjugate)
839
+ R_c_list = []
840
+ for x in self.A:
841
+ r = {word for word in R_set if word[0] == x}
842
+ R_c_list.append(r)
843
+ R_set.difference_update(r)
844
+ return R_c_list
845
+
846
+ def coset_representative(self, coset):
847
+ '''
848
+ Compute the coset representative of a given coset.
849
+
850
+ Examples
851
+ ========
852
+
853
+ >>> from sympy.combinatorics import free_group
854
+ >>> from sympy.combinatorics.fp_groups import FpGroup, coset_enumeration_r
855
+ >>> F, x, y = free_group("x, y")
856
+ >>> f = FpGroup(F, [x**3, y**3, x**-1*y**-1*x*y])
857
+ >>> C = coset_enumeration_r(f, [x])
858
+ >>> C.compress()
859
+ >>> C.table
860
+ [[0, 0, 1, 2], [1, 1, 2, 0], [2, 2, 0, 1]]
861
+ >>> C.coset_representative(0)
862
+ <identity>
863
+ >>> C.coset_representative(1)
864
+ y
865
+ >>> C.coset_representative(2)
866
+ y**-1
867
+
868
+ '''
869
+ for x in self.A:
870
+ gamma = self.table[coset][self.A_dict[x]]
871
+ if coset == 0:
872
+ return self.fp_group.identity
873
+ if gamma < coset:
874
+ return self.coset_representative(gamma)*x**-1
875
+
876
+ ##############################
877
+ # Modified Methods #
878
+ ##############################
879
+
880
+ def modified_define(self, alpha, x):
881
+ r"""
882
+ Define a function p_p from from [1..n] to A* as
883
+ an additional component of the modified coset table.
884
+
885
+ Parameters
886
+ ==========
887
+
888
+ \alpha \in \Omega
889
+ x \in A*
890
+
891
+ See Also
892
+ ========
893
+
894
+ define
895
+
896
+ """
897
+ self.define(alpha, x, modified=True)
898
+
899
+ def modified_scan(self, alpha, w, y, fill=False):
900
+ r"""
901
+ Parameters
902
+ ==========
903
+ \alpha \in \Omega
904
+ w \in A*
905
+ y \in (YUY^-1)
906
+ fill -- `modified_scan_and_fill` when set to True.
907
+
908
+ See Also
909
+ ========
910
+
911
+ scan
912
+ """
913
+ self.scan(alpha, w, y=y, fill=fill, modified=True)
914
+
915
+ def modified_scan_and_fill(self, alpha, w, y):
916
+ self.modified_scan(alpha, w, y, fill=True)
917
+
918
+ def modified_merge(self, k, lamda, w, q):
919
+ r"""
920
+ Parameters
921
+ ==========
922
+
923
+ 'k', 'lamda' -- the two class representatives to be merged.
924
+ q -- queue of length l of elements to be deleted from `\Omega` *.
925
+ w -- Word in (YUY^-1)
926
+
927
+ See Also
928
+ ========
929
+
930
+ merge
931
+ """
932
+ self.merge(k, lamda, q, w=w, modified=True)
933
+
934
+ def modified_rep(self, k):
935
+ r"""
936
+ Parameters
937
+ ==========
938
+
939
+ `k \in [0 \ldots n-1]`
940
+
941
+ See Also
942
+ ========
943
+
944
+ rep
945
+ """
946
+ self.rep(k, modified=True)
947
+
948
+ def modified_coincidence(self, alpha, beta, w):
949
+ r"""
950
+ Parameters
951
+ ==========
952
+
953
+ A coincident pair `\alpha, \beta \in \Omega, w \in Y \cup Y^{-1}`
954
+
955
+ See Also
956
+ ========
957
+
958
+ coincidence
959
+
960
+ """
961
+ self.coincidence(alpha, beta, w=w, modified=True)
962
+
963
+ ###############################################################################
964
+ # COSET ENUMERATION #
965
+ ###############################################################################
966
+
967
+ # relator-based method
968
+ def coset_enumeration_r(fp_grp, Y, max_cosets=None, draft=None,
969
+ incomplete=False, modified=False):
970
+ """
971
+ This is easier of the two implemented methods of coset enumeration.
972
+ and is often called the HLT method, after Hazelgrove, Leech, Trotter
973
+ The idea is that we make use of ``scan_and_fill`` makes new definitions
974
+ whenever the scan is incomplete to enable the scan to complete; this way
975
+ we fill in the gaps in the scan of the relator or subgroup generator,
976
+ that's why the name relator-based method.
977
+
978
+ An instance of `CosetTable` for `fp_grp` can be passed as the keyword
979
+ argument `draft` in which case the coset enumeration will start with
980
+ that instance and attempt to complete it.
981
+
982
+ When `incomplete` is `True` and the function is unable to complete for
983
+ some reason, the partially complete table will be returned.
984
+
985
+ # TODO: complete the docstring
986
+
987
+ See Also
988
+ ========
989
+
990
+ scan_and_fill,
991
+
992
+ Examples
993
+ ========
994
+
995
+ >>> from sympy.combinatorics.free_groups import free_group
996
+ >>> from sympy.combinatorics.fp_groups import FpGroup, coset_enumeration_r
997
+ >>> F, x, y = free_group("x, y")
998
+
999
+ # Example 5.1 from [1]
1000
+ >>> f = FpGroup(F, [x**3, y**3, x**-1*y**-1*x*y])
1001
+ >>> C = coset_enumeration_r(f, [x])
1002
+ >>> for i in range(len(C.p)):
1003
+ ... if C.p[i] == i:
1004
+ ... print(C.table[i])
1005
+ [0, 0, 1, 2]
1006
+ [1, 1, 2, 0]
1007
+ [2, 2, 0, 1]
1008
+ >>> C.p
1009
+ [0, 1, 2, 1, 1]
1010
+
1011
+ # Example from exercises Q2 [1]
1012
+ >>> f = FpGroup(F, [x**2*y**2, y**-1*x*y*x**-3])
1013
+ >>> C = coset_enumeration_r(f, [])
1014
+ >>> C.compress(); C.standardize()
1015
+ >>> C.table
1016
+ [[1, 2, 3, 4],
1017
+ [5, 0, 6, 7],
1018
+ [0, 5, 7, 6],
1019
+ [7, 6, 5, 0],
1020
+ [6, 7, 0, 5],
1021
+ [2, 1, 4, 3],
1022
+ [3, 4, 2, 1],
1023
+ [4, 3, 1, 2]]
1024
+
1025
+ # Example 5.2
1026
+ >>> f = FpGroup(F, [x**2, y**3, (x*y)**3])
1027
+ >>> Y = [x*y]
1028
+ >>> C = coset_enumeration_r(f, Y)
1029
+ >>> for i in range(len(C.p)):
1030
+ ... if C.p[i] == i:
1031
+ ... print(C.table[i])
1032
+ [1, 1, 2, 1]
1033
+ [0, 0, 0, 2]
1034
+ [3, 3, 1, 0]
1035
+ [2, 2, 3, 3]
1036
+
1037
+ # Example 5.3
1038
+ >>> f = FpGroup(F, [x**2*y**2, x**3*y**5])
1039
+ >>> Y = []
1040
+ >>> C = coset_enumeration_r(f, Y)
1041
+ >>> for i in range(len(C.p)):
1042
+ ... if C.p[i] == i:
1043
+ ... print(C.table[i])
1044
+ [1, 3, 1, 3]
1045
+ [2, 0, 2, 0]
1046
+ [3, 1, 3, 1]
1047
+ [0, 2, 0, 2]
1048
+
1049
+ # Example 5.4
1050
+ >>> F, a, b, c, d, e = free_group("a, b, c, d, e")
1051
+ >>> f = FpGroup(F, [a*b*c**-1, b*c*d**-1, c*d*e**-1, d*e*a**-1, e*a*b**-1])
1052
+ >>> Y = [a]
1053
+ >>> C = coset_enumeration_r(f, Y)
1054
+ >>> for i in range(len(C.p)):
1055
+ ... if C.p[i] == i:
1056
+ ... print(C.table[i])
1057
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
1058
+
1059
+ # example of "compress" method
1060
+ >>> C.compress()
1061
+ >>> C.table
1062
+ [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
1063
+
1064
+ # Exercises Pg. 161, Q2.
1065
+ >>> F, x, y = free_group("x, y")
1066
+ >>> f = FpGroup(F, [x**2*y**2, y**-1*x*y*x**-3])
1067
+ >>> Y = []
1068
+ >>> C = coset_enumeration_r(f, Y)
1069
+ >>> C.compress()
1070
+ >>> C.standardize()
1071
+ >>> C.table
1072
+ [[1, 2, 3, 4],
1073
+ [5, 0, 6, 7],
1074
+ [0, 5, 7, 6],
1075
+ [7, 6, 5, 0],
1076
+ [6, 7, 0, 5],
1077
+ [2, 1, 4, 3],
1078
+ [3, 4, 2, 1],
1079
+ [4, 3, 1, 2]]
1080
+
1081
+ # John J. Cannon; Lucien A. Dimino; George Havas; Jane M. Watson
1082
+ # Mathematics of Computation, Vol. 27, No. 123. (Jul., 1973), pp. 463-490
1083
+ # from 1973chwd.pdf
1084
+ # Table 1. Ex. 1
1085
+ >>> F, r, s, t = free_group("r, s, t")
1086
+ >>> E1 = FpGroup(F, [t**-1*r*t*r**-2, r**-1*s*r*s**-2, s**-1*t*s*t**-2])
1087
+ >>> C = coset_enumeration_r(E1, [r])
1088
+ >>> for i in range(len(C.p)):
1089
+ ... if C.p[i] == i:
1090
+ ... print(C.table[i])
1091
+ [0, 0, 0, 0, 0, 0]
1092
+
1093
+ Ex. 2
1094
+ >>> F, a, b = free_group("a, b")
1095
+ >>> Cox = FpGroup(F, [a**6, b**6, (a*b)**2, (a**2*b**2)**2, (a**3*b**3)**5])
1096
+ >>> C = coset_enumeration_r(Cox, [a])
1097
+ >>> index = 0
1098
+ >>> for i in range(len(C.p)):
1099
+ ... if C.p[i] == i:
1100
+ ... index += 1
1101
+ >>> index
1102
+ 500
1103
+
1104
+ # Ex. 3
1105
+ >>> F, a, b = free_group("a, b")
1106
+ >>> B_2_4 = FpGroup(F, [a**4, b**4, (a*b)**4, (a**-1*b)**4, (a**2*b)**4, \
1107
+ (a*b**2)**4, (a**2*b**2)**4, (a**-1*b*a*b)**4, (a*b**-1*a*b)**4])
1108
+ >>> C = coset_enumeration_r(B_2_4, [a])
1109
+ >>> index = 0
1110
+ >>> for i in range(len(C.p)):
1111
+ ... if C.p[i] == i:
1112
+ ... index += 1
1113
+ >>> index
1114
+ 1024
1115
+
1116
+ References
1117
+ ==========
1118
+
1119
+ .. [1] Holt, D., Eick, B., O'Brien, E.
1120
+ "Handbook of computational group theory"
1121
+
1122
+ """
1123
+ # 1. Initialize a coset table C for < X|R >
1124
+ C = CosetTable(fp_grp, Y, max_cosets=max_cosets)
1125
+ # Define coset table methods.
1126
+ if modified:
1127
+ _scan_and_fill = C.modified_scan_and_fill
1128
+ _define = C.modified_define
1129
+ else:
1130
+ _scan_and_fill = C.scan_and_fill
1131
+ _define = C.define
1132
+ if draft:
1133
+ C.table = draft.table[:]
1134
+ C.p = draft.p[:]
1135
+ R = fp_grp.relators
1136
+ A_dict = C.A_dict
1137
+ p = C.p
1138
+ for i in range(len(Y)):
1139
+ if modified:
1140
+ _scan_and_fill(0, Y[i], C._grp.generators[i])
1141
+ else:
1142
+ _scan_and_fill(0, Y[i])
1143
+ alpha = 0
1144
+ while alpha < C.n:
1145
+ if p[alpha] == alpha:
1146
+ try:
1147
+ for w in R:
1148
+ if modified:
1149
+ _scan_and_fill(alpha, w, C._grp.identity)
1150
+ else:
1151
+ _scan_and_fill(alpha, w)
1152
+ # if alpha was eliminated during the scan then break
1153
+ if p[alpha] < alpha:
1154
+ break
1155
+ if p[alpha] == alpha:
1156
+ for x in A_dict:
1157
+ if C.table[alpha][A_dict[x]] is None:
1158
+ _define(alpha, x)
1159
+ except ValueError as e:
1160
+ if incomplete:
1161
+ return C
1162
+ raise e
1163
+ alpha += 1
1164
+ return C
1165
+
1166
+ def modified_coset_enumeration_r(fp_grp, Y, max_cosets=None, draft=None,
1167
+ incomplete=False):
1168
+ r"""
1169
+ Introduce a new set of symbols y \in Y that correspond to the
1170
+ generators of the subgroup. Store the elements of Y as a
1171
+ word P[\alpha, x] and compute the coset table similar to that of
1172
+ the regular coset enumeration methods.
1173
+
1174
+ Examples
1175
+ ========
1176
+
1177
+ >>> from sympy.combinatorics.free_groups import free_group
1178
+ >>> from sympy.combinatorics.fp_groups import FpGroup
1179
+ >>> from sympy.combinatorics.coset_table import modified_coset_enumeration_r
1180
+ >>> F, x, y = free_group("x, y")
1181
+ >>> f = FpGroup(F, [x**3, y**3, x**-1*y**-1*x*y])
1182
+ >>> C = modified_coset_enumeration_r(f, [x])
1183
+ >>> C.table
1184
+ [[0, 0, 1, 2], [1, 1, 2, 0], [2, 2, 0, 1], [None, 1, None, None], [1, 3, None, None]]
1185
+
1186
+ See Also
1187
+ ========
1188
+
1189
+ coset_enumertation_r
1190
+
1191
+ References
1192
+ ==========
1193
+
1194
+ .. [1] Holt, D., Eick, B., O'Brien, E.,
1195
+ "Handbook of Computational Group Theory",
1196
+ Section 5.3.2
1197
+ """
1198
+ return coset_enumeration_r(fp_grp, Y, max_cosets=max_cosets, draft=draft,
1199
+ incomplete=incomplete, modified=True)
1200
+
1201
+ # Pg. 166
1202
+ # coset-table based method
1203
+ def coset_enumeration_c(fp_grp, Y, max_cosets=None, draft=None,
1204
+ incomplete=False):
1205
+ """
1206
+ >>> from sympy.combinatorics.free_groups import free_group
1207
+ >>> from sympy.combinatorics.fp_groups import FpGroup, coset_enumeration_c
1208
+ >>> F, x, y = free_group("x, y")
1209
+ >>> f = FpGroup(F, [x**3, y**3, x**-1*y**-1*x*y])
1210
+ >>> C = coset_enumeration_c(f, [x])
1211
+ >>> C.table
1212
+ [[0, 0, 1, 2], [1, 1, 2, 0], [2, 2, 0, 1]]
1213
+
1214
+ """
1215
+ # Initialize a coset table C for < X|R >
1216
+ X = fp_grp.generators
1217
+ R = fp_grp.relators
1218
+ C = CosetTable(fp_grp, Y, max_cosets=max_cosets)
1219
+ if draft:
1220
+ C.table = draft.table[:]
1221
+ C.p = draft.p[:]
1222
+ C.deduction_stack = draft.deduction_stack
1223
+ for alpha, x in product(range(len(C.table)), X):
1224
+ if C.table[alpha][C.A_dict[x]] is not None:
1225
+ C.deduction_stack.append((alpha, x))
1226
+ A = C.A
1227
+ # replace all the elements by cyclic reductions
1228
+ R_cyc_red = [rel.identity_cyclic_reduction() for rel in R]
1229
+ R_c = list(chain.from_iterable((rel.cyclic_conjugates(), (rel**-1).cyclic_conjugates()) \
1230
+ for rel in R_cyc_red))
1231
+ R_set = set()
1232
+ for conjugate in R_c:
1233
+ R_set = R_set.union(conjugate)
1234
+ # a list of subsets of R_c whose words start with "x".
1235
+ R_c_list = []
1236
+ for x in C.A:
1237
+ r = {word for word in R_set if word[0] == x}
1238
+ R_c_list.append(r)
1239
+ R_set.difference_update(r)
1240
+ for w in Y:
1241
+ C.scan_and_fill_c(0, w)
1242
+ for x in A:
1243
+ C.process_deductions(R_c_list[C.A_dict[x]], R_c_list[C.A_dict_inv[x]])
1244
+ alpha = 0
1245
+ while alpha < len(C.table):
1246
+ if C.p[alpha] == alpha:
1247
+ try:
1248
+ for x in C.A:
1249
+ if C.p[alpha] != alpha:
1250
+ break
1251
+ if C.table[alpha][C.A_dict[x]] is None:
1252
+ C.define_c(alpha, x)
1253
+ C.process_deductions(R_c_list[C.A_dict[x]], R_c_list[C.A_dict_inv[x]])
1254
+ except ValueError as e:
1255
+ if incomplete:
1256
+ return C
1257
+ raise e
1258
+ alpha += 1
1259
+ return C
.venv/lib/python3.13/site-packages/sympy/combinatorics/fp_groups.py ADDED
@@ -0,0 +1,1352 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Finitely Presented Groups and its algorithms. """
2
+
3
+ from sympy.core.singleton import S
4
+ from sympy.core.symbol import symbols
5
+ from sympy.combinatorics.free_groups import (FreeGroup, FreeGroupElement,
6
+ free_group)
7
+ from sympy.combinatorics.rewritingsystem import RewritingSystem
8
+ from sympy.combinatorics.coset_table import (CosetTable,
9
+ coset_enumeration_r,
10
+ coset_enumeration_c)
11
+ from sympy.combinatorics import PermutationGroup
12
+ from sympy.matrices.normalforms import invariant_factors
13
+ from sympy.matrices import Matrix
14
+ from sympy.polys.polytools import gcd
15
+ from sympy.printing.defaults import DefaultPrinting
16
+ from sympy.utilities import public
17
+ from sympy.utilities.magic import pollute
18
+
19
+ from itertools import product
20
+
21
+
22
+ @public
23
+ def fp_group(fr_grp, relators=()):
24
+ _fp_group = FpGroup(fr_grp, relators)
25
+ return (_fp_group,) + tuple(_fp_group._generators)
26
+
27
+ @public
28
+ def xfp_group(fr_grp, relators=()):
29
+ _fp_group = FpGroup(fr_grp, relators)
30
+ return (_fp_group, _fp_group._generators)
31
+
32
+ # Does not work. Both symbols and pollute are undefined. Never tested.
33
+ @public
34
+ def vfp_group(fr_grpm, relators):
35
+ _fp_group = FpGroup(symbols, relators)
36
+ pollute([sym.name for sym in _fp_group.symbols], _fp_group.generators)
37
+ return _fp_group
38
+
39
+
40
+ def _parse_relators(rels):
41
+ """Parse the passed relators."""
42
+ return rels
43
+
44
+
45
+ ###############################################################################
46
+ # FINITELY PRESENTED GROUPS #
47
+ ###############################################################################
48
+
49
+
50
+ class FpGroup(DefaultPrinting):
51
+ """
52
+ The FpGroup would take a FreeGroup and a list/tuple of relators, the
53
+ relators would be specified in such a way that each of them be equal to the
54
+ identity of the provided free group.
55
+
56
+ """
57
+ is_group = True
58
+ is_FpGroup = True
59
+ is_PermutationGroup = False
60
+
61
+ def __init__(self, fr_grp, relators):
62
+ relators = _parse_relators(relators)
63
+ self.free_group = fr_grp
64
+ self.relators = relators
65
+ self.generators = self._generators()
66
+ self.dtype = type("FpGroupElement", (FpGroupElement,), {"group": self})
67
+
68
+ # CosetTable instance on identity subgroup
69
+ self._coset_table = None
70
+ # returns whether coset table on identity subgroup
71
+ # has been standardized
72
+ self._is_standardized = False
73
+
74
+ self._order = None
75
+ self._center = None
76
+
77
+ self._rewriting_system = RewritingSystem(self)
78
+ self._perm_isomorphism = None
79
+ return
80
+
81
+ def _generators(self):
82
+ return self.free_group.generators
83
+
84
+ def make_confluent(self):
85
+ '''
86
+ Try to make the group's rewriting system confluent
87
+
88
+ '''
89
+ self._rewriting_system.make_confluent()
90
+ return
91
+
92
+ def reduce(self, word):
93
+ '''
94
+ Return the reduced form of `word` in `self` according to the group's
95
+ rewriting system. If it's confluent, the reduced form is the unique normal
96
+ form of the word in the group.
97
+
98
+ '''
99
+ return self._rewriting_system.reduce(word)
100
+
101
+ def equals(self, word1, word2):
102
+ '''
103
+ Compare `word1` and `word2` for equality in the group
104
+ using the group's rewriting system. If the system is
105
+ confluent, the returned answer is necessarily correct.
106
+ (If it is not, `False` could be returned in some cases
107
+ where in fact `word1 == word2`)
108
+
109
+ '''
110
+ if self.reduce(word1*word2**-1) == self.identity:
111
+ return True
112
+ elif self._rewriting_system.is_confluent:
113
+ return False
114
+ return None
115
+
116
+ @property
117
+ def identity(self):
118
+ return self.free_group.identity
119
+
120
+ def __contains__(self, g):
121
+ return g in self.free_group
122
+
123
+ def subgroup(self, gens, C=None, homomorphism=False):
124
+ '''
125
+ Return the subgroup generated by `gens` using the
126
+ Reidemeister-Schreier algorithm
127
+ homomorphism -- When set to True, return a dictionary containing the images
128
+ of the presentation generators in the original group.
129
+
130
+ Examples
131
+ ========
132
+
133
+ >>> from sympy.combinatorics.fp_groups import FpGroup
134
+ >>> from sympy.combinatorics import free_group
135
+ >>> F, x, y = free_group("x, y")
136
+ >>> f = FpGroup(F, [x**3, y**5, (x*y)**2])
137
+ >>> H = [x*y, x**-1*y**-1*x*y*x]
138
+ >>> K, T = f.subgroup(H, homomorphism=True)
139
+ >>> T(K.generators)
140
+ [x*y, x**-1*y**2*x**-1]
141
+
142
+ '''
143
+
144
+ if not all(isinstance(g, FreeGroupElement) for g in gens):
145
+ raise ValueError("Generators must be `FreeGroupElement`s")
146
+ if not all(g.group == self.free_group for g in gens):
147
+ raise ValueError("Given generators are not members of the group")
148
+ if homomorphism:
149
+ g, rels, _gens = reidemeister_presentation(self, gens, C=C, homomorphism=True)
150
+ else:
151
+ g, rels = reidemeister_presentation(self, gens, C=C)
152
+ if g:
153
+ g = FpGroup(g[0].group, rels)
154
+ else:
155
+ g = FpGroup(free_group('')[0], [])
156
+ if homomorphism:
157
+ from sympy.combinatorics.homomorphisms import homomorphism
158
+ return g, homomorphism(g, self, g.generators, _gens, check=False)
159
+ return g
160
+
161
+ def coset_enumeration(self, H, strategy="relator_based", max_cosets=None,
162
+ draft=None, incomplete=False):
163
+ """
164
+ Return an instance of ``coset table``, when Todd-Coxeter algorithm is
165
+ run over the ``self`` with ``H`` as subgroup, using ``strategy``
166
+ argument as strategy. The returned coset table is compressed but not
167
+ standardized.
168
+
169
+ An instance of `CosetTable` for `fp_grp` can be passed as the keyword
170
+ argument `draft` in which case the coset enumeration will start with
171
+ that instance and attempt to complete it.
172
+
173
+ When `incomplete` is `True` and the function is unable to complete for
174
+ some reason, the partially complete table will be returned.
175
+
176
+ """
177
+ if not max_cosets:
178
+ max_cosets = CosetTable.coset_table_max_limit
179
+ if strategy == 'relator_based':
180
+ C = coset_enumeration_r(self, H, max_cosets=max_cosets,
181
+ draft=draft, incomplete=incomplete)
182
+ else:
183
+ C = coset_enumeration_c(self, H, max_cosets=max_cosets,
184
+ draft=draft, incomplete=incomplete)
185
+ if C.is_complete():
186
+ C.compress()
187
+ return C
188
+
189
+ def standardize_coset_table(self):
190
+ """
191
+ Standardized the coset table ``self`` and makes the internal variable
192
+ ``_is_standardized`` equal to ``True``.
193
+
194
+ """
195
+ self._coset_table.standardize()
196
+ self._is_standardized = True
197
+
198
+ def coset_table(self, H, strategy="relator_based", max_cosets=None,
199
+ draft=None, incomplete=False):
200
+ """
201
+ Return the mathematical coset table of ``self`` in ``H``.
202
+
203
+ """
204
+ if not H:
205
+ if self._coset_table is not None:
206
+ if not self._is_standardized:
207
+ self.standardize_coset_table()
208
+ else:
209
+ C = self.coset_enumeration([], strategy, max_cosets=max_cosets,
210
+ draft=draft, incomplete=incomplete)
211
+ self._coset_table = C
212
+ self.standardize_coset_table()
213
+ return self._coset_table.table
214
+ else:
215
+ C = self.coset_enumeration(H, strategy, max_cosets=max_cosets,
216
+ draft=draft, incomplete=incomplete)
217
+ C.standardize()
218
+ return C.table
219
+
220
+ def order(self, strategy="relator_based"):
221
+ """
222
+ Returns the order of the finitely presented group ``self``. It uses
223
+ the coset enumeration with identity group as subgroup, i.e ``H=[]``.
224
+
225
+ Examples
226
+ ========
227
+
228
+ >>> from sympy.combinatorics import free_group
229
+ >>> from sympy.combinatorics.fp_groups import FpGroup
230
+ >>> F, x, y = free_group("x, y")
231
+ >>> f = FpGroup(F, [x, y**2])
232
+ >>> f.order(strategy="coset_table_based")
233
+ 2
234
+
235
+ """
236
+ if self._order is not None:
237
+ return self._order
238
+ if self._coset_table is not None:
239
+ self._order = len(self._coset_table.table)
240
+ elif len(self.relators) == 0:
241
+ self._order = self.free_group.order()
242
+ elif len(self.generators) == 1:
243
+ self._order = abs(gcd([r.array_form[0][1] for r in self.relators]))
244
+ elif self._is_infinite():
245
+ self._order = S.Infinity
246
+ else:
247
+ gens, C = self._finite_index_subgroup()
248
+ if C:
249
+ ind = len(C.table)
250
+ self._order = ind*self.subgroup(gens, C=C).order()
251
+ else:
252
+ self._order = self.index([])
253
+ return self._order
254
+
255
+ def _is_infinite(self):
256
+ '''
257
+ Test if the group is infinite. Return `True` if the test succeeds
258
+ and `None` otherwise
259
+
260
+ '''
261
+ used_gens = set()
262
+ for r in self.relators:
263
+ used_gens.update(r.contains_generators())
264
+ if not set(self.generators) <= used_gens:
265
+ return True
266
+ # Abelianisation test: check is the abelianisation is infinite
267
+ abelian_rels = []
268
+ for rel in self.relators:
269
+ abelian_rels.append([rel.exponent_sum(g) for g in self.generators])
270
+ m = Matrix(Matrix(abelian_rels))
271
+ if 0 in invariant_factors(m):
272
+ return True
273
+ else:
274
+ return None
275
+
276
+
277
+ def _finite_index_subgroup(self, s=None):
278
+ '''
279
+ Find the elements of `self` that generate a finite index subgroup
280
+ and, if found, return the list of elements and the coset table of `self` by
281
+ the subgroup, otherwise return `(None, None)`
282
+
283
+ '''
284
+ gen = self.most_frequent_generator()
285
+ rels = list(self.generators)
286
+ rels.extend(self.relators)
287
+ if not s:
288
+ if len(self.generators) == 2:
289
+ s = [gen] + [g for g in self.generators if g != gen]
290
+ else:
291
+ rand = self.free_group.identity
292
+ i = 0
293
+ while ((rand in rels or rand**-1 in rels or rand.is_identity)
294
+ and i<10):
295
+ rand = self.random()
296
+ i += 1
297
+ s = [gen, rand] + [g for g in self.generators if g != gen]
298
+ mid = (len(s)+1)//2
299
+ half1 = s[:mid]
300
+ half2 = s[mid:]
301
+ draft1 = None
302
+ draft2 = None
303
+ m = 200
304
+ C = None
305
+ while not C and (m/2 < CosetTable.coset_table_max_limit):
306
+ m = min(m, CosetTable.coset_table_max_limit)
307
+ draft1 = self.coset_enumeration(half1, max_cosets=m,
308
+ draft=draft1, incomplete=True)
309
+ if draft1.is_complete():
310
+ C = draft1
311
+ half = half1
312
+ else:
313
+ draft2 = self.coset_enumeration(half2, max_cosets=m,
314
+ draft=draft2, incomplete=True)
315
+ if draft2.is_complete():
316
+ C = draft2
317
+ half = half2
318
+ if not C:
319
+ m *= 2
320
+ if not C:
321
+ return None, None
322
+ C.compress()
323
+ return half, C
324
+
325
+ def most_frequent_generator(self):
326
+ gens = self.generators
327
+ rels = self.relators
328
+ freqs = [sum(r.generator_count(g) for r in rels) for g in gens]
329
+ return gens[freqs.index(max(freqs))]
330
+
331
+ def random(self):
332
+ import random
333
+ r = self.free_group.identity
334
+ for i in range(random.randint(2,3)):
335
+ r = r*random.choice(self.generators)**random.choice([1,-1])
336
+ return r
337
+
338
+ def index(self, H, strategy="relator_based"):
339
+ """
340
+ Return the index of subgroup ``H`` in group ``self``.
341
+
342
+ Examples
343
+ ========
344
+
345
+ >>> from sympy.combinatorics import free_group
346
+ >>> from sympy.combinatorics.fp_groups import FpGroup
347
+ >>> F, x, y = free_group("x, y")
348
+ >>> f = FpGroup(F, [x**5, y**4, y*x*y**3*x**3])
349
+ >>> f.index([x])
350
+ 4
351
+
352
+ """
353
+ # TODO: use |G:H| = |G|/|H| (currently H can't be made into a group)
354
+ # when we know |G| and |H|
355
+
356
+ if H == []:
357
+ return self.order()
358
+ else:
359
+ C = self.coset_enumeration(H, strategy)
360
+ return len(C.table)
361
+
362
+ def __str__(self):
363
+ if self.free_group.rank > 30:
364
+ str_form = "<fp group with %s generators>" % self.free_group.rank
365
+ else:
366
+ str_form = "<fp group on the generators %s>" % str(self.generators)
367
+ return str_form
368
+
369
+ __repr__ = __str__
370
+
371
+ #==============================================================================
372
+ # PERMUTATION GROUP METHODS
373
+ #==============================================================================
374
+
375
+ def _to_perm_group(self):
376
+ '''
377
+ Return an isomorphic permutation group and the isomorphism.
378
+ The implementation is dependent on coset enumeration so
379
+ will only terminate for finite groups.
380
+
381
+ '''
382
+ from sympy.combinatorics import Permutation
383
+ from sympy.combinatorics.homomorphisms import homomorphism
384
+ if self.order() is S.Infinity:
385
+ raise NotImplementedError("Permutation presentation of infinite "
386
+ "groups is not implemented")
387
+ if self._perm_isomorphism:
388
+ T = self._perm_isomorphism
389
+ P = T.image()
390
+ else:
391
+ C = self.coset_table([])
392
+ gens = self.generators
393
+ images = [[C[i][2*gens.index(g)] for i in range(len(C))] for g in gens]
394
+ images = [Permutation(i) for i in images]
395
+ P = PermutationGroup(images)
396
+ T = homomorphism(self, P, gens, images, check=False)
397
+ self._perm_isomorphism = T
398
+ return P, T
399
+
400
+ def _perm_group_list(self, method_name, *args):
401
+ '''
402
+ Given the name of a `PermutationGroup` method (returning a subgroup
403
+ or a list of subgroups) and (optionally) additional arguments it takes,
404
+ return a list or a list of lists containing the generators of this (or
405
+ these) subgroups in terms of the generators of `self`.
406
+
407
+ '''
408
+ P, T = self._to_perm_group()
409
+ perm_result = getattr(P, method_name)(*args)
410
+ single = False
411
+ if isinstance(perm_result, PermutationGroup):
412
+ perm_result, single = [perm_result], True
413
+ result = []
414
+ for group in perm_result:
415
+ gens = group.generators
416
+ result.append(T.invert(gens))
417
+ return result[0] if single else result
418
+
419
+ def derived_series(self):
420
+ '''
421
+ Return the list of lists containing the generators
422
+ of the subgroups in the derived series of `self`.
423
+
424
+ '''
425
+ return self._perm_group_list('derived_series')
426
+
427
+ def lower_central_series(self):
428
+ '''
429
+ Return the list of lists containing the generators
430
+ of the subgroups in the lower central series of `self`.
431
+
432
+ '''
433
+ return self._perm_group_list('lower_central_series')
434
+
435
+ def center(self):
436
+ '''
437
+ Return the list of generators of the center of `self`.
438
+
439
+ '''
440
+ return self._perm_group_list('center')
441
+
442
+
443
+ def derived_subgroup(self):
444
+ '''
445
+ Return the list of generators of the derived subgroup of `self`.
446
+
447
+ '''
448
+ return self._perm_group_list('derived_subgroup')
449
+
450
+
451
+ def centralizer(self, other):
452
+ '''
453
+ Return the list of generators of the centralizer of `other`
454
+ (a list of elements of `self`) in `self`.
455
+
456
+ '''
457
+ T = self._to_perm_group()[1]
458
+ other = T(other)
459
+ return self._perm_group_list('centralizer', other)
460
+
461
+ def normal_closure(self, other):
462
+ '''
463
+ Return the list of generators of the normal closure of `other`
464
+ (a list of elements of `self`) in `self`.
465
+
466
+ '''
467
+ T = self._to_perm_group()[1]
468
+ other = T(other)
469
+ return self._perm_group_list('normal_closure', other)
470
+
471
+ def _perm_property(self, attr):
472
+ '''
473
+ Given an attribute of a `PermutationGroup`, return
474
+ its value for a permutation group isomorphic to `self`.
475
+
476
+ '''
477
+ P = self._to_perm_group()[0]
478
+ return getattr(P, attr)
479
+
480
+ @property
481
+ def is_abelian(self):
482
+ '''
483
+ Check if `self` is abelian.
484
+
485
+ '''
486
+ return self._perm_property("is_abelian")
487
+
488
+ @property
489
+ def is_nilpotent(self):
490
+ '''
491
+ Check if `self` is nilpotent.
492
+
493
+ '''
494
+ return self._perm_property("is_nilpotent")
495
+
496
+ @property
497
+ def is_solvable(self):
498
+ '''
499
+ Check if `self` is solvable.
500
+
501
+ '''
502
+ return self._perm_property("is_solvable")
503
+
504
+ @property
505
+ def elements(self):
506
+ '''
507
+ List the elements of `self`.
508
+
509
+ '''
510
+ P, T = self._to_perm_group()
511
+ return T.invert(P.elements)
512
+
513
+ @property
514
+ def is_cyclic(self):
515
+ """
516
+ Return ``True`` if group is Cyclic.
517
+
518
+ """
519
+ if len(self.generators) <= 1:
520
+ return True
521
+ try:
522
+ P, T = self._to_perm_group()
523
+ except NotImplementedError:
524
+ raise NotImplementedError("Check for infinite Cyclic group "
525
+ "is not implemented")
526
+ return P.is_cyclic
527
+
528
+ def abelian_invariants(self):
529
+ """
530
+ Return Abelian Invariants of a group.
531
+ """
532
+ try:
533
+ P, T = self._to_perm_group()
534
+ except NotImplementedError:
535
+ raise NotImplementedError("abelian invariants is not implemented"
536
+ "for infinite group")
537
+ return P.abelian_invariants()
538
+
539
+ def composition_series(self):
540
+ """
541
+ Return subnormal series of maximum length for a group.
542
+ """
543
+ try:
544
+ P, T = self._to_perm_group()
545
+ except NotImplementedError:
546
+ raise NotImplementedError("composition series is not implemented"
547
+ "for infinite group")
548
+ return P.composition_series()
549
+
550
+
551
+ class FpSubgroup(DefaultPrinting):
552
+ '''
553
+ The class implementing a subgroup of an FpGroup or a FreeGroup
554
+ (only finite index subgroups are supported at this point). This
555
+ is to be used if one wishes to check if an element of the original
556
+ group belongs to the subgroup
557
+
558
+ '''
559
+ def __init__(self, G, gens, normal=False):
560
+ super().__init__()
561
+ self.parent = G
562
+ self.generators = list({g for g in gens if g != G.identity})
563
+ self._min_words = None #for use in __contains__
564
+ self.C = None
565
+ self.normal = normal
566
+
567
+ def __contains__(self, g):
568
+
569
+ if isinstance(self.parent, FreeGroup):
570
+ if self._min_words is None:
571
+ # make _min_words - a list of subwords such that
572
+ # g is in the subgroup if and only if it can be
573
+ # partitioned into these subwords. Infinite families of
574
+ # subwords are presented by tuples, e.g. (r, w)
575
+ # stands for the family of subwords r*w**n*r**-1
576
+
577
+ def _process(w):
578
+ # this is to be used before adding new words
579
+ # into _min_words; if the word w is not cyclically
580
+ # reduced, it will generate an infinite family of
581
+ # subwords so should be written as a tuple;
582
+ # if it is, w**-1 should be added to the list
583
+ # as well
584
+ p, r = w.cyclic_reduction(removed=True)
585
+ if not r.is_identity:
586
+ return [(r, p)]
587
+ else:
588
+ return [w, w**-1]
589
+
590
+ # make the initial list
591
+ gens = []
592
+ for w in self.generators:
593
+ if self.normal:
594
+ w = w.cyclic_reduction()
595
+ gens.extend(_process(w))
596
+
597
+ for w1 in gens:
598
+ for w2 in gens:
599
+ # if w1 and w2 are equal or are inverses, continue
600
+ if w1 == w2 or (not isinstance(w1, tuple)
601
+ and w1**-1 == w2):
602
+ continue
603
+
604
+ # if the start of one word is the inverse of the
605
+ # end of the other, their multiple should be added
606
+ # to _min_words because of cancellation
607
+ if isinstance(w1, tuple):
608
+ # start, end
609
+ s1, s2 = w1[0][0], w1[0][0]**-1
610
+ else:
611
+ s1, s2 = w1[0], w1[len(w1)-1]
612
+
613
+ if isinstance(w2, tuple):
614
+ # start, end
615
+ r1, r2 = w2[0][0], w2[0][0]**-1
616
+ else:
617
+ r1, r2 = w2[0], w2[len(w1)-1]
618
+
619
+ # p1 and p2 are w1 and w2 or, in case when
620
+ # w1 or w2 is an infinite family, a representative
621
+ p1, p2 = w1, w2
622
+ if isinstance(w1, tuple):
623
+ p1 = w1[0]*w1[1]*w1[0]**-1
624
+ if isinstance(w2, tuple):
625
+ p2 = w2[0]*w2[1]*w2[0]**-1
626
+
627
+ # add the product of the words to the list is necessary
628
+ if r1**-1 == s2 and not (p1*p2).is_identity:
629
+ new = _process(p1*p2)
630
+ if new not in gens:
631
+ gens.extend(new)
632
+
633
+ if r2**-1 == s1 and not (p2*p1).is_identity:
634
+ new = _process(p2*p1)
635
+ if new not in gens:
636
+ gens.extend(new)
637
+
638
+ self._min_words = gens
639
+
640
+ min_words = self._min_words
641
+
642
+ def _is_subword(w):
643
+ # check if w is a word in _min_words or one of
644
+ # the infinite families in it
645
+ w, r = w.cyclic_reduction(removed=True)
646
+ if r.is_identity or self.normal:
647
+ return w in min_words
648
+ else:
649
+ t = [s[1] for s in min_words if isinstance(s, tuple)
650
+ and s[0] == r]
651
+ return [s for s in t if w.power_of(s)] != []
652
+
653
+ # store the solution of words for which the result of
654
+ # _word_break (below) is known
655
+ known = {}
656
+
657
+ def _word_break(w):
658
+ # check if w can be written as a product of words
659
+ # in min_words
660
+ if len(w) == 0:
661
+ return True
662
+ i = 0
663
+ while i < len(w):
664
+ i += 1
665
+ prefix = w.subword(0, i)
666
+ if not _is_subword(prefix):
667
+ continue
668
+ rest = w.subword(i, len(w))
669
+ if rest not in known:
670
+ known[rest] = _word_break(rest)
671
+ if known[rest]:
672
+ return True
673
+ return False
674
+
675
+ if self.normal:
676
+ g = g.cyclic_reduction()
677
+ return _word_break(g)
678
+ else:
679
+ if self.C is None:
680
+ C = self.parent.coset_enumeration(self.generators)
681
+ self.C = C
682
+ i = 0
683
+ C = self.C
684
+ for j in range(len(g)):
685
+ i = C.table[i][C.A_dict[g[j]]]
686
+ return i == 0
687
+
688
+ def order(self):
689
+ if not self.generators:
690
+ return S.One
691
+ if isinstance(self.parent, FreeGroup):
692
+ return S.Infinity
693
+ if self.C is None:
694
+ C = self.parent.coset_enumeration(self.generators)
695
+ self.C = C
696
+ # This is valid because `len(self.C.table)` (the index of the subgroup)
697
+ # will always be finite - otherwise coset enumeration doesn't terminate
698
+ return self.parent.order()/len(self.C.table)
699
+
700
+ def to_FpGroup(self):
701
+ if isinstance(self.parent, FreeGroup):
702
+ gen_syms = [('x_%d'%i) for i in range(len(self.generators))]
703
+ return free_group(', '.join(gen_syms))[0]
704
+ return self.parent.subgroup(C=self.C)
705
+
706
+ def __str__(self):
707
+ if len(self.generators) > 30:
708
+ str_form = "<fp subgroup with %s generators>" % len(self.generators)
709
+ else:
710
+ str_form = "<fp subgroup on the generators %s>" % str(self.generators)
711
+ return str_form
712
+
713
+ __repr__ = __str__
714
+
715
+
716
+ ###############################################################################
717
+ # LOW INDEX SUBGROUPS #
718
+ ###############################################################################
719
+
720
+ def low_index_subgroups(G, N, Y=()):
721
+ """
722
+ Implements the Low Index Subgroups algorithm, i.e find all subgroups of
723
+ ``G`` upto a given index ``N``. This implements the method described in
724
+ [Sim94]. This procedure involves a backtrack search over incomplete Coset
725
+ Tables, rather than over forced coincidences.
726
+
727
+ Parameters
728
+ ==========
729
+
730
+ G: An FpGroup < X|R >
731
+ N: positive integer, representing the maximum index value for subgroups
732
+ Y: (an optional argument) specifying a list of subgroup generators, such
733
+ that each of the resulting subgroup contains the subgroup generated by Y.
734
+
735
+ Examples
736
+ ========
737
+
738
+ >>> from sympy.combinatorics import free_group
739
+ >>> from sympy.combinatorics.fp_groups import FpGroup, low_index_subgroups
740
+ >>> F, x, y = free_group("x, y")
741
+ >>> f = FpGroup(F, [x**2, y**3, (x*y)**4])
742
+ >>> L = low_index_subgroups(f, 4)
743
+ >>> for coset_table in L:
744
+ ... print(coset_table.table)
745
+ [[0, 0, 0, 0]]
746
+ [[0, 0, 1, 2], [1, 1, 2, 0], [3, 3, 0, 1], [2, 2, 3, 3]]
747
+ [[0, 0, 1, 2], [2, 2, 2, 0], [1, 1, 0, 1]]
748
+ [[1, 1, 0, 0], [0, 0, 1, 1]]
749
+
750
+ References
751
+ ==========
752
+
753
+ .. [1] Holt, D., Eick, B., O'Brien, E.
754
+ "Handbook of Computational Group Theory"
755
+ Section 5.4
756
+
757
+ .. [2] Marston Conder and Peter Dobcsanyi
758
+ "Applications and Adaptions of the Low Index Subgroups Procedure"
759
+
760
+ """
761
+ C = CosetTable(G, [])
762
+ R = G.relators
763
+ # length chosen for the length of the short relators
764
+ len_short_rel = 5
765
+ # elements of R2 only checked at the last step for complete
766
+ # coset tables
767
+ R2 = {rel for rel in R if len(rel) > len_short_rel}
768
+ # elements of R1 are used in inner parts of the process to prune
769
+ # branches of the search tree,
770
+ R1 = {rel.identity_cyclic_reduction() for rel in set(R) - R2}
771
+ R1_c_list = C.conjugates(R1)
772
+ S = []
773
+ descendant_subgroups(S, C, R1_c_list, C.A[0], R2, N, Y)
774
+ return S
775
+
776
+
777
+ def descendant_subgroups(S, C, R1_c_list, x, R2, N, Y):
778
+ A_dict = C.A_dict
779
+ A_dict_inv = C.A_dict_inv
780
+ if C.is_complete():
781
+ # if C is complete then it only needs to test
782
+ # whether the relators in R2 are satisfied
783
+ for w, alpha in product(R2, C.omega):
784
+ if not C.scan_check(alpha, w):
785
+ return
786
+ # relators in R2 are satisfied, append the table to list
787
+ S.append(C)
788
+ else:
789
+ # find the first undefined entry in Coset Table
790
+ for alpha, x in product(range(len(C.table)), C.A):
791
+ if C.table[alpha][A_dict[x]] is None:
792
+ # this is "x" in pseudo-code (using "y" makes it clear)
793
+ undefined_coset, undefined_gen = alpha, x
794
+ break
795
+ # for filling up the undefine entry we try all possible values
796
+ # of beta in Omega or beta = n where beta^(undefined_gen^-1) is undefined
797
+ reach = C.omega + [C.n]
798
+ for beta in reach:
799
+ if beta < N:
800
+ if beta == C.n or C.table[beta][A_dict_inv[undefined_gen]] is None:
801
+ try_descendant(S, C, R1_c_list, R2, N, undefined_coset, \
802
+ undefined_gen, beta, Y)
803
+
804
+
805
+ def try_descendant(S, C, R1_c_list, R2, N, alpha, x, beta, Y):
806
+ r"""
807
+ Solves the problem of trying out each individual possibility
808
+ for `\alpha^x.
809
+
810
+ """
811
+ D = C.copy()
812
+ if beta == D.n and beta < N:
813
+ D.table.append([None]*len(D.A))
814
+ D.p.append(beta)
815
+ D.table[alpha][D.A_dict[x]] = beta
816
+ D.table[beta][D.A_dict_inv[x]] = alpha
817
+ D.deduction_stack.append((alpha, x))
818
+ if not D.process_deductions_check(R1_c_list[D.A_dict[x]], \
819
+ R1_c_list[D.A_dict_inv[x]]):
820
+ return
821
+ for w in Y:
822
+ if not D.scan_check(0, w):
823
+ return
824
+ if first_in_class(D, Y):
825
+ descendant_subgroups(S, D, R1_c_list, x, R2, N, Y)
826
+
827
+
828
+ def first_in_class(C, Y=()):
829
+ """
830
+ Checks whether the subgroup ``H=G1`` corresponding to the Coset Table
831
+ could possibly be the canonical representative of its conjugacy class.
832
+
833
+ Parameters
834
+ ==========
835
+
836
+ C: CosetTable
837
+
838
+ Returns
839
+ =======
840
+
841
+ bool: True/False
842
+
843
+ If this returns False, then no descendant of C can have that property, and
844
+ so we can abandon C. If it returns True, then we need to process further
845
+ the node of the search tree corresponding to C, and so we call
846
+ ``descendant_subgroups`` recursively on C.
847
+
848
+ Examples
849
+ ========
850
+
851
+ >>> from sympy.combinatorics import free_group
852
+ >>> from sympy.combinatorics.fp_groups import FpGroup, CosetTable, first_in_class
853
+ >>> F, x, y = free_group("x, y")
854
+ >>> f = FpGroup(F, [x**2, y**3, (x*y)**4])
855
+ >>> C = CosetTable(f, [])
856
+ >>> C.table = [[0, 0, None, None]]
857
+ >>> first_in_class(C)
858
+ True
859
+ >>> C.table = [[1, 1, 1, None], [0, 0, None, 1]]; C.p = [0, 1]
860
+ >>> first_in_class(C)
861
+ True
862
+ >>> C.table = [[1, 1, 2, 1], [0, 0, 0, None], [None, None, None, 0]]
863
+ >>> C.p = [0, 1, 2]
864
+ >>> first_in_class(C)
865
+ False
866
+ >>> C.table = [[1, 1, 1, 2], [0, 0, 2, 0], [2, None, 0, 1]]
867
+ >>> first_in_class(C)
868
+ False
869
+
870
+ # TODO:: Sims points out in [Sim94] that performance can be improved by
871
+ # remembering some of the information computed by ``first_in_class``. If
872
+ # the ``continue alpha`` statement is executed at line 14, then the same thing
873
+ # will happen for that value of alpha in any descendant of the table C, and so
874
+ # the values the values of alpha for which this occurs could profitably be
875
+ # stored and passed through to the descendants of C. Of course this would
876
+ # make the code more complicated.
877
+
878
+ # The code below is taken directly from the function on page 208 of [Sim94]
879
+ # nu[alpha]
880
+
881
+ """
882
+ n = C.n
883
+ # lamda is the largest numbered point in Omega_c_alpha which is currently defined
884
+ lamda = -1
885
+ # for alpha in Omega_c, nu[alpha] is the point in Omega_c_alpha corresponding to alpha
886
+ nu = [None]*n
887
+ # for alpha in Omega_c_alpha, mu[alpha] is the point in Omega_c corresponding to alpha
888
+ mu = [None]*n
889
+ # mutually nu and mu are the mutually-inverse equivalence maps between
890
+ # Omega_c_alpha and Omega_c
891
+ next_alpha = False
892
+ # For each 0!=alpha in [0 .. nc-1], we start by constructing the equivalent
893
+ # standardized coset table C_alpha corresponding to H_alpha
894
+ for alpha in range(1, n):
895
+ # reset nu to "None" after previous value of alpha
896
+ for beta in range(lamda+1):
897
+ nu[mu[beta]] = None
898
+ # we only want to reject our current table in favour of a preceding
899
+ # table in the ordering in which 1 is replaced by alpha, if the subgroup
900
+ # G_alpha corresponding to this preceding table definitely contains the
901
+ # given subgroup
902
+ for w in Y:
903
+ # TODO: this should support input of a list of general words
904
+ # not just the words which are in "A" (i.e gen and gen^-1)
905
+ if C.table[alpha][C.A_dict[w]] != alpha:
906
+ # continue with alpha
907
+ next_alpha = True
908
+ break
909
+ if next_alpha:
910
+ next_alpha = False
911
+ continue
912
+ # try alpha as the new point 0 in Omega_C_alpha
913
+ mu[0] = alpha
914
+ nu[alpha] = 0
915
+ # compare corresponding entries in C and C_alpha
916
+ lamda = 0
917
+ for beta in range(n):
918
+ for x in C.A:
919
+ gamma = C.table[beta][C.A_dict[x]]
920
+ delta = C.table[mu[beta]][C.A_dict[x]]
921
+ # if either of the entries is undefined,
922
+ # we move with next alpha
923
+ if gamma is None or delta is None:
924
+ # continue with alpha
925
+ next_alpha = True
926
+ break
927
+ if nu[delta] is None:
928
+ # delta becomes the next point in Omega_C_alpha
929
+ lamda += 1
930
+ nu[delta] = lamda
931
+ mu[lamda] = delta
932
+ if nu[delta] < gamma:
933
+ return False
934
+ if nu[delta] > gamma:
935
+ # continue with alpha
936
+ next_alpha = True
937
+ break
938
+ if next_alpha:
939
+ next_alpha = False
940
+ break
941
+ return True
942
+
943
+ #========================================================================
944
+ # Simplifying Presentation
945
+ #========================================================================
946
+
947
+ def simplify_presentation(*args, change_gens=False):
948
+ '''
949
+ For an instance of `FpGroup`, return a simplified isomorphic copy of
950
+ the group (e.g. remove redundant generators or relators). Alternatively,
951
+ a list of generators and relators can be passed in which case the
952
+ simplified lists will be returned.
953
+
954
+ By default, the generators of the group are unchanged. If you would
955
+ like to remove redundant generators, set the keyword argument
956
+ `change_gens = True`.
957
+
958
+ '''
959
+ if len(args) == 1:
960
+ if not isinstance(args[0], FpGroup):
961
+ raise TypeError("The argument must be an instance of FpGroup")
962
+ G = args[0]
963
+ gens, rels = simplify_presentation(G.generators, G.relators,
964
+ change_gens=change_gens)
965
+ if gens:
966
+ return FpGroup(gens[0].group, rels)
967
+ return FpGroup(FreeGroup([]), [])
968
+ elif len(args) == 2:
969
+ gens, rels = args[0][:], args[1][:]
970
+ if not gens:
971
+ return gens, rels
972
+ identity = gens[0].group.identity
973
+ else:
974
+ if len(args) == 0:
975
+ m = "Not enough arguments"
976
+ else:
977
+ m = "Too many arguments"
978
+ raise RuntimeError(m)
979
+
980
+ prev_gens = []
981
+ prev_rels = []
982
+ while not set(prev_rels) == set(rels):
983
+ prev_rels = rels
984
+ while change_gens and not set(prev_gens) == set(gens):
985
+ prev_gens = gens
986
+ gens, rels = elimination_technique_1(gens, rels, identity)
987
+ rels = _simplify_relators(rels)
988
+
989
+ if change_gens:
990
+ syms = [g.array_form[0][0] for g in gens]
991
+ F = free_group(syms)[0]
992
+ identity = F.identity
993
+ gens = F.generators
994
+ subs = dict(zip(syms, gens))
995
+ for j, r in enumerate(rels):
996
+ a = r.array_form
997
+ rel = identity
998
+ for sym, p in a:
999
+ rel = rel*subs[sym]**p
1000
+ rels[j] = rel
1001
+ return gens, rels
1002
+
1003
+ def _simplify_relators(rels):
1004
+ """
1005
+ Simplifies a set of relators. All relators are checked to see if they are
1006
+ of the form `gen^n`. If any such relators are found then all other relators
1007
+ are processed for strings in the `gen` known order.
1008
+
1009
+ Examples
1010
+ ========
1011
+
1012
+ >>> from sympy.combinatorics import free_group
1013
+ >>> from sympy.combinatorics.fp_groups import _simplify_relators
1014
+ >>> F, x, y = free_group("x, y")
1015
+ >>> w1 = [x**2*y**4, x**3]
1016
+ >>> _simplify_relators(w1)
1017
+ [x**3, x**-1*y**4]
1018
+
1019
+ >>> w2 = [x**2*y**-4*x**5, x**3, x**2*y**8, y**5]
1020
+ >>> _simplify_relators(w2)
1021
+ [x**-1*y**-2, x**-1*y*x**-1, x**3, y**5]
1022
+
1023
+ >>> w3 = [x**6*y**4, x**4]
1024
+ >>> _simplify_relators(w3)
1025
+ [x**4, x**2*y**4]
1026
+
1027
+ >>> w4 = [x**2, x**5, y**3]
1028
+ >>> _simplify_relators(w4)
1029
+ [x, y**3]
1030
+
1031
+ """
1032
+ rels = rels[:]
1033
+
1034
+ if not rels:
1035
+ return []
1036
+
1037
+ identity = rels[0].group.identity
1038
+
1039
+ # build dictionary with "gen: n" where gen^n is one of the relators
1040
+ exps = {}
1041
+ for i in range(len(rels)):
1042
+ rel = rels[i]
1043
+ if rel.number_syllables() == 1:
1044
+ g = rel[0]
1045
+ exp = abs(rel.array_form[0][1])
1046
+ if rel.array_form[0][1] < 0:
1047
+ rels[i] = rels[i]**-1
1048
+ g = g**-1
1049
+ if g in exps:
1050
+ exp = gcd(exp, exps[g].array_form[0][1])
1051
+ exps[g] = g**exp
1052
+
1053
+ one_syllables_words = list(exps.values())
1054
+ # decrease some of the exponents in relators, making use of the single
1055
+ # syllable relators
1056
+ for i, rel in enumerate(rels):
1057
+ if rel in one_syllables_words:
1058
+ continue
1059
+ rel = rel.eliminate_words(one_syllables_words, _all = True)
1060
+ # if rels[i] contains g**n where abs(n) is greater than half of the power p
1061
+ # of g in exps, g**n can be replaced by g**(n-p) (or g**(p-n) if n<0)
1062
+ for g in rel.contains_generators():
1063
+ if g in exps:
1064
+ exp = exps[g].array_form[0][1]
1065
+ max_exp = (exp + 1)//2
1066
+ rel = rel.eliminate_word(g**(max_exp), g**(max_exp-exp), _all = True)
1067
+ rel = rel.eliminate_word(g**(-max_exp), g**(-(max_exp-exp)), _all = True)
1068
+ rels[i] = rel
1069
+
1070
+ rels = [r.identity_cyclic_reduction() for r in rels]
1071
+
1072
+ rels += one_syllables_words # include one_syllable_words in the list of relators
1073
+ rels = list(set(rels)) # get unique values in rels
1074
+ rels.sort()
1075
+
1076
+ # remove <identity> entries in rels
1077
+ try:
1078
+ rels.remove(identity)
1079
+ except ValueError:
1080
+ pass
1081
+ return rels
1082
+
1083
+ # Pg 350, section 2.5.1 from [2]
1084
+ def elimination_technique_1(gens, rels, identity):
1085
+ rels = rels[:]
1086
+ # the shorter relators are examined first so that generators selected for
1087
+ # elimination will have shorter strings as equivalent
1088
+ rels.sort()
1089
+ gens = gens[:]
1090
+ redundant_gens = {}
1091
+ redundant_rels = []
1092
+ used_gens = set()
1093
+ # examine each relator in relator list for any generator occurring exactly
1094
+ # once
1095
+ for rel in rels:
1096
+ # don't look for a redundant generator in a relator which
1097
+ # depends on previously found ones
1098
+ contained_gens = rel.contains_generators()
1099
+ if any(g in contained_gens for g in redundant_gens):
1100
+ continue
1101
+ contained_gens = list(contained_gens)
1102
+ contained_gens.sort(reverse = True)
1103
+ for gen in contained_gens:
1104
+ if rel.generator_count(gen) == 1 and gen not in used_gens:
1105
+ k = rel.exponent_sum(gen)
1106
+ gen_index = rel.index(gen**k)
1107
+ bk = rel.subword(gen_index + 1, len(rel))
1108
+ fw = rel.subword(0, gen_index)
1109
+ chi = bk*fw
1110
+ redundant_gens[gen] = chi**(-1*k)
1111
+ used_gens.update(chi.contains_generators())
1112
+ redundant_rels.append(rel)
1113
+ break
1114
+ rels = [r for r in rels if r not in redundant_rels]
1115
+ # eliminate the redundant generators from remaining relators
1116
+ rels = [r.eliminate_words(redundant_gens, _all = True).identity_cyclic_reduction() for r in rels]
1117
+ rels = list(set(rels))
1118
+ try:
1119
+ rels.remove(identity)
1120
+ except ValueError:
1121
+ pass
1122
+ gens = [g for g in gens if g not in redundant_gens]
1123
+ return gens, rels
1124
+
1125
+ ###############################################################################
1126
+ # SUBGROUP PRESENTATIONS #
1127
+ ###############################################################################
1128
+
1129
+ # Pg 175 [1]
1130
+ def define_schreier_generators(C, homomorphism=False):
1131
+ '''
1132
+ Parameters
1133
+ ==========
1134
+
1135
+ C -- Coset table.
1136
+ homomorphism -- When set to True, return a dictionary containing the images
1137
+ of the presentation generators in the original group.
1138
+ '''
1139
+ y = []
1140
+ gamma = 1
1141
+ f = C.fp_group
1142
+ X = f.generators
1143
+ if homomorphism:
1144
+ # `_gens` stores the elements of the parent group to
1145
+ # to which the schreier generators correspond to.
1146
+ _gens = {}
1147
+ # compute the schreier Traversal
1148
+ tau = {}
1149
+ tau[0] = f.identity
1150
+ C.P = [[None]*len(C.A) for i in range(C.n)]
1151
+ for alpha, x in product(C.omega, C.A):
1152
+ beta = C.table[alpha][C.A_dict[x]]
1153
+ if beta == gamma:
1154
+ C.P[alpha][C.A_dict[x]] = "<identity>"
1155
+ C.P[beta][C.A_dict_inv[x]] = "<identity>"
1156
+ gamma += 1
1157
+ if homomorphism:
1158
+ tau[beta] = tau[alpha]*x
1159
+ elif x in X and C.P[alpha][C.A_dict[x]] is None:
1160
+ y_alpha_x = '%s_%s' % (x, alpha)
1161
+ y.append(y_alpha_x)
1162
+ C.P[alpha][C.A_dict[x]] = y_alpha_x
1163
+ if homomorphism:
1164
+ _gens[y_alpha_x] = tau[alpha]*x*tau[beta]**-1
1165
+ grp_gens = list(free_group(', '.join(y)))
1166
+ C._schreier_free_group = grp_gens.pop(0)
1167
+ C._schreier_generators = grp_gens
1168
+ if homomorphism:
1169
+ C._schreier_gen_elem = _gens
1170
+ # replace all elements of P by, free group elements
1171
+ for i, j in product(range(len(C.P)), range(len(C.A))):
1172
+ # if equals "<identity>", replace by identity element
1173
+ if C.P[i][j] == "<identity>":
1174
+ C.P[i][j] = C._schreier_free_group.identity
1175
+ elif isinstance(C.P[i][j], str):
1176
+ r = C._schreier_generators[y.index(C.P[i][j])]
1177
+ C.P[i][j] = r
1178
+ beta = C.table[i][j]
1179
+ C.P[beta][j + 1] = r**-1
1180
+
1181
+ def reidemeister_relators(C):
1182
+ R = C.fp_group.relators
1183
+ rels = [rewrite(C, coset, word) for word in R for coset in range(C.n)]
1184
+ order_1_gens = {i for i in rels if len(i) == 1}
1185
+
1186
+ # remove all the order 1 generators from relators
1187
+ rels = list(filter(lambda rel: rel not in order_1_gens, rels))
1188
+
1189
+ # replace order 1 generators by identity element in reidemeister relators
1190
+ for i in range(len(rels)):
1191
+ w = rels[i]
1192
+ w = w.eliminate_words(order_1_gens, _all=True)
1193
+ rels[i] = w
1194
+
1195
+ C._schreier_generators = [i for i in C._schreier_generators
1196
+ if not (i in order_1_gens or i**-1 in order_1_gens)]
1197
+
1198
+ # Tietze transformation 1 i.e TT_1
1199
+ # remove cyclic conjugate elements from relators
1200
+ i = 0
1201
+ while i < len(rels):
1202
+ w = rels[i]
1203
+ j = i + 1
1204
+ while j < len(rels):
1205
+ if w.is_cyclic_conjugate(rels[j]):
1206
+ del rels[j]
1207
+ else:
1208
+ j += 1
1209
+ i += 1
1210
+
1211
+ C._reidemeister_relators = rels
1212
+
1213
+
1214
+ def rewrite(C, alpha, w):
1215
+ """
1216
+ Parameters
1217
+ ==========
1218
+
1219
+ C: CosetTable
1220
+ alpha: A live coset
1221
+ w: A word in `A*`
1222
+
1223
+ Returns
1224
+ =======
1225
+
1226
+ rho(tau(alpha), w)
1227
+
1228
+ Examples
1229
+ ========
1230
+
1231
+ >>> from sympy.combinatorics.fp_groups import FpGroup, CosetTable, define_schreier_generators, rewrite
1232
+ >>> from sympy.combinatorics import free_group
1233
+ >>> F, x, y = free_group("x, y")
1234
+ >>> f = FpGroup(F, [x**2, y**3, (x*y)**6])
1235
+ >>> C = CosetTable(f, [])
1236
+ >>> C.table = [[1, 1, 2, 3], [0, 0, 4, 5], [4, 4, 3, 0], [5, 5, 0, 2], [2, 2, 5, 1], [3, 3, 1, 4]]
1237
+ >>> C.p = [0, 1, 2, 3, 4, 5]
1238
+ >>> define_schreier_generators(C)
1239
+ >>> rewrite(C, 0, (x*y)**6)
1240
+ x_4*y_2*x_3*x_1*x_2*y_4*x_5
1241
+
1242
+ """
1243
+ v = C._schreier_free_group.identity
1244
+ for i in range(len(w)):
1245
+ x_i = w[i]
1246
+ v = v*C.P[alpha][C.A_dict[x_i]]
1247
+ alpha = C.table[alpha][C.A_dict[x_i]]
1248
+ return v
1249
+
1250
+ # Pg 350, section 2.5.2 from [2]
1251
+ def elimination_technique_2(C):
1252
+ """
1253
+ This technique eliminates one generator at a time. Heuristically this
1254
+ seems superior in that we may select for elimination the generator with
1255
+ shortest equivalent string at each stage.
1256
+
1257
+ >>> from sympy.combinatorics import free_group
1258
+ >>> from sympy.combinatorics.fp_groups import FpGroup, coset_enumeration_r, \
1259
+ reidemeister_relators, define_schreier_generators, elimination_technique_2
1260
+ >>> F, x, y = free_group("x, y")
1261
+ >>> f = FpGroup(F, [x**3, y**5, (x*y)**2]); H = [x*y, x**-1*y**-1*x*y*x]
1262
+ >>> C = coset_enumeration_r(f, H)
1263
+ >>> C.compress(); C.standardize()
1264
+ >>> define_schreier_generators(C)
1265
+ >>> reidemeister_relators(C)
1266
+ >>> elimination_technique_2(C)
1267
+ ([y_1, y_2], [y_2**-3, y_2*y_1*y_2*y_1*y_2*y_1, y_1**2])
1268
+
1269
+ """
1270
+ rels = C._reidemeister_relators
1271
+ rels.sort(reverse=True)
1272
+ gens = C._schreier_generators
1273
+ for i in range(len(gens) - 1, -1, -1):
1274
+ rel = rels[i]
1275
+ for j in range(len(gens) - 1, -1, -1):
1276
+ gen = gens[j]
1277
+ if rel.generator_count(gen) == 1:
1278
+ k = rel.exponent_sum(gen)
1279
+ gen_index = rel.index(gen**k)
1280
+ bk = rel.subword(gen_index + 1, len(rel))
1281
+ fw = rel.subword(0, gen_index)
1282
+ rep_by = (bk*fw)**(-1*k)
1283
+ del rels[i]
1284
+ del gens[j]
1285
+ rels = [rel.eliminate_word(gen, rep_by) for rel in rels]
1286
+ break
1287
+ C._reidemeister_relators = rels
1288
+ C._schreier_generators = gens
1289
+ return C._schreier_generators, C._reidemeister_relators
1290
+
1291
+ def reidemeister_presentation(fp_grp, H, C=None, homomorphism=False):
1292
+ """
1293
+ Parameters
1294
+ ==========
1295
+
1296
+ fp_group: A finitely presented group, an instance of FpGroup
1297
+ H: A subgroup whose presentation is to be found, given as a list
1298
+ of words in generators of `fp_grp`
1299
+ homomorphism: When set to True, return a homomorphism from the subgroup
1300
+ to the parent group
1301
+
1302
+ Examples
1303
+ ========
1304
+
1305
+ >>> from sympy.combinatorics import free_group
1306
+ >>> from sympy.combinatorics.fp_groups import FpGroup, reidemeister_presentation
1307
+ >>> F, x, y = free_group("x, y")
1308
+
1309
+ Example 5.6 Pg. 177 from [1]
1310
+ >>> f = FpGroup(F, [x**3, y**5, (x*y)**2])
1311
+ >>> H = [x*y, x**-1*y**-1*x*y*x]
1312
+ >>> reidemeister_presentation(f, H)
1313
+ ((y_1, y_2), (y_1**2, y_2**3, y_2*y_1*y_2*y_1*y_2*y_1))
1314
+
1315
+ Example 5.8 Pg. 183 from [1]
1316
+ >>> f = FpGroup(F, [x**3, y**3, (x*y)**3])
1317
+ >>> H = [x*y, x*y**-1]
1318
+ >>> reidemeister_presentation(f, H)
1319
+ ((x_0, y_0), (x_0**3, y_0**3, x_0*y_0*x_0*y_0*x_0*y_0))
1320
+
1321
+ Exercises Q2. Pg 187 from [1]
1322
+ >>> f = FpGroup(F, [x**2*y**2, y**-1*x*y*x**-3])
1323
+ >>> H = [x]
1324
+ >>> reidemeister_presentation(f, H)
1325
+ ((x_0,), (x_0**4,))
1326
+
1327
+ Example 5.9 Pg. 183 from [1]
1328
+ >>> f = FpGroup(F, [x**3*y**-3, (x*y)**3, (x*y**-1)**2])
1329
+ >>> H = [x]
1330
+ >>> reidemeister_presentation(f, H)
1331
+ ((x_0,), (x_0**6,))
1332
+
1333
+ """
1334
+ if not C:
1335
+ C = coset_enumeration_r(fp_grp, H)
1336
+ C.compress(); C.standardize()
1337
+ define_schreier_generators(C, homomorphism=homomorphism)
1338
+ reidemeister_relators(C)
1339
+ gens, rels = C._schreier_generators, C._reidemeister_relators
1340
+ gens, rels = simplify_presentation(gens, rels, change_gens=True)
1341
+
1342
+ C.schreier_generators = tuple(gens)
1343
+ C.reidemeister_relators = tuple(rels)
1344
+
1345
+ if homomorphism:
1346
+ _gens = [C._schreier_gen_elem[str(gen)] for gen in gens]
1347
+ return C.schreier_generators, C.reidemeister_relators, _gens
1348
+
1349
+ return C.schreier_generators, C.reidemeister_relators
1350
+
1351
+
1352
+ FpGroupElement = FreeGroupElement
.venv/lib/python3.13/site-packages/sympy/combinatorics/free_groups.py ADDED
@@ -0,0 +1,1360 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from sympy.core import S
4
+ from sympy.core.expr import Expr
5
+ from sympy.core.symbol import Symbol, symbols as _symbols
6
+ from sympy.core.sympify import CantSympify
7
+ from sympy.printing.defaults import DefaultPrinting
8
+ from sympy.utilities import public
9
+ from sympy.utilities.iterables import flatten, is_sequence
10
+ from sympy.utilities.magic import pollute
11
+ from sympy.utilities.misc import as_int
12
+
13
+
14
+ @public
15
+ def free_group(symbols):
16
+ """Construct a free group returning ``(FreeGroup, (f_0, f_1, ..., f_(n-1))``.
17
+
18
+ Parameters
19
+ ==========
20
+
21
+ symbols : str, Symbol/Expr or sequence of str, Symbol/Expr (may be empty)
22
+
23
+ Examples
24
+ ========
25
+
26
+ >>> from sympy.combinatorics import free_group
27
+ >>> F, x, y, z = free_group("x, y, z")
28
+ >>> F
29
+ <free group on the generators (x, y, z)>
30
+ >>> x**2*y**-1
31
+ x**2*y**-1
32
+ >>> type(_)
33
+ <class 'sympy.combinatorics.free_groups.FreeGroupElement'>
34
+
35
+ """
36
+ _free_group = FreeGroup(symbols)
37
+ return (_free_group,) + tuple(_free_group.generators)
38
+
39
+ @public
40
+ def xfree_group(symbols):
41
+ """Construct a free group returning ``(FreeGroup, (f_0, f_1, ..., f_(n-1)))``.
42
+
43
+ Parameters
44
+ ==========
45
+
46
+ symbols : str, Symbol/Expr or sequence of str, Symbol/Expr (may be empty)
47
+
48
+ Examples
49
+ ========
50
+
51
+ >>> from sympy.combinatorics.free_groups import xfree_group
52
+ >>> F, (x, y, z) = xfree_group("x, y, z")
53
+ >>> F
54
+ <free group on the generators (x, y, z)>
55
+ >>> y**2*x**-2*z**-1
56
+ y**2*x**-2*z**-1
57
+ >>> type(_)
58
+ <class 'sympy.combinatorics.free_groups.FreeGroupElement'>
59
+
60
+ """
61
+ _free_group = FreeGroup(symbols)
62
+ return (_free_group, _free_group.generators)
63
+
64
+ @public
65
+ def vfree_group(symbols):
66
+ """Construct a free group and inject ``f_0, f_1, ..., f_(n-1)`` as symbols
67
+ into the global namespace.
68
+
69
+ Parameters
70
+ ==========
71
+
72
+ symbols : str, Symbol/Expr or sequence of str, Symbol/Expr (may be empty)
73
+
74
+ Examples
75
+ ========
76
+
77
+ >>> from sympy.combinatorics.free_groups import vfree_group
78
+ >>> vfree_group("x, y, z")
79
+ <free group on the generators (x, y, z)>
80
+ >>> x**2*y**-2*z # noqa: F821
81
+ x**2*y**-2*z
82
+ >>> type(_)
83
+ <class 'sympy.combinatorics.free_groups.FreeGroupElement'>
84
+
85
+ """
86
+ _free_group = FreeGroup(symbols)
87
+ pollute([sym.name for sym in _free_group.symbols], _free_group.generators)
88
+ return _free_group
89
+
90
+
91
+ def _parse_symbols(symbols):
92
+ if not symbols:
93
+ return ()
94
+ if isinstance(symbols, str):
95
+ return _symbols(symbols, seq=True)
96
+ elif isinstance(symbols, (Expr, FreeGroupElement)):
97
+ return (symbols,)
98
+ elif is_sequence(symbols):
99
+ if all(isinstance(s, str) for s in symbols):
100
+ return _symbols(symbols)
101
+ elif all(isinstance(s, Expr) for s in symbols):
102
+ return symbols
103
+ raise ValueError("The type of `symbols` must be one of the following: "
104
+ "a str, Symbol/Expr or a sequence of "
105
+ "one of these types")
106
+
107
+
108
+ ##############################################################################
109
+ # FREE GROUP #
110
+ ##############################################################################
111
+
112
+ _free_group_cache: dict[int, FreeGroup] = {}
113
+
114
+ class FreeGroup(DefaultPrinting):
115
+ """
116
+ Free group with finite or infinite number of generators. Its input API
117
+ is that of a str, Symbol/Expr or a sequence of one of
118
+ these types (which may be empty)
119
+
120
+ See Also
121
+ ========
122
+
123
+ sympy.polys.rings.PolyRing
124
+
125
+ References
126
+ ==========
127
+
128
+ .. [1] https://www.gap-system.org/Manuals/doc/ref/chap37.html
129
+
130
+ .. [2] https://en.wikipedia.org/wiki/Free_group
131
+
132
+ """
133
+ is_associative = True
134
+ is_group = True
135
+ is_FreeGroup = True
136
+ is_PermutationGroup = False
137
+ relators: list[Expr] = []
138
+
139
+ def __new__(cls, symbols):
140
+ symbols = tuple(_parse_symbols(symbols))
141
+ rank = len(symbols)
142
+ _hash = hash((cls.__name__, symbols, rank))
143
+ obj = _free_group_cache.get(_hash)
144
+
145
+ if obj is None:
146
+ obj = object.__new__(cls)
147
+ obj._hash = _hash
148
+ obj._rank = rank
149
+ # dtype method is used to create new instances of FreeGroupElement
150
+ obj.dtype = type("FreeGroupElement", (FreeGroupElement,), {"group": obj})
151
+ obj.symbols = symbols
152
+ obj.generators = obj._generators()
153
+ obj._gens_set = set(obj.generators)
154
+ for symbol, generator in zip(obj.symbols, obj.generators):
155
+ if isinstance(symbol, Symbol):
156
+ name = symbol.name
157
+ if hasattr(obj, name):
158
+ setattr(obj, name, generator)
159
+
160
+ _free_group_cache[_hash] = obj
161
+
162
+ return obj
163
+
164
+ def __getnewargs__(self):
165
+ """Return a tuple of arguments that must be passed to __new__ in order to support pickling this object."""
166
+ return (self.symbols,)
167
+
168
+ def __getstate__(self):
169
+ # Don't pickle any fields because they are regenerated within __new__
170
+ return None
171
+
172
+ def _generators(group):
173
+ """Returns the generators of the FreeGroup.
174
+
175
+ Examples
176
+ ========
177
+
178
+ >>> from sympy.combinatorics import free_group
179
+ >>> F, x, y, z = free_group("x, y, z")
180
+ >>> F.generators
181
+ (x, y, z)
182
+
183
+ """
184
+ gens = []
185
+ for sym in group.symbols:
186
+ elm = ((sym, 1),)
187
+ gens.append(group.dtype(elm))
188
+ return tuple(gens)
189
+
190
+ def clone(self, symbols=None):
191
+ return self.__class__(symbols or self.symbols)
192
+
193
+ def __contains__(self, i):
194
+ """Return True if ``i`` is contained in FreeGroup."""
195
+ if not isinstance(i, FreeGroupElement):
196
+ return False
197
+ group = i.group
198
+ return self == group
199
+
200
+ def __hash__(self):
201
+ return self._hash
202
+
203
+ def __len__(self):
204
+ return self.rank
205
+
206
+ def __str__(self):
207
+ if self.rank > 30:
208
+ str_form = "<free group with %s generators>" % self.rank
209
+ else:
210
+ str_form = "<free group on the generators "
211
+ gens = self.generators
212
+ str_form += str(gens) + ">"
213
+ return str_form
214
+
215
+ __repr__ = __str__
216
+
217
+ def __getitem__(self, index):
218
+ symbols = self.symbols[index]
219
+ return self.clone(symbols=symbols)
220
+
221
+ def __eq__(self, other):
222
+ """No ``FreeGroup`` is equal to any "other" ``FreeGroup``.
223
+ """
224
+ return self is other
225
+
226
+ def index(self, gen):
227
+ """Return the index of the generator `gen` from ``(f_0, ..., f_(n-1))``.
228
+
229
+ Examples
230
+ ========
231
+
232
+ >>> from sympy.combinatorics import free_group
233
+ >>> F, x, y = free_group("x, y")
234
+ >>> F.index(y)
235
+ 1
236
+ >>> F.index(x)
237
+ 0
238
+
239
+ """
240
+ if isinstance(gen, self.dtype):
241
+ return self.generators.index(gen)
242
+ else:
243
+ raise ValueError("expected a generator of Free Group %s, got %s" % (self, gen))
244
+
245
+ def order(self):
246
+ """Return the order of the free group.
247
+
248
+ Examples
249
+ ========
250
+
251
+ >>> from sympy.combinatorics import free_group
252
+ >>> F, x, y = free_group("x, y")
253
+ >>> F.order()
254
+ oo
255
+
256
+ >>> free_group("")[0].order()
257
+ 1
258
+
259
+ """
260
+ if self.rank == 0:
261
+ return S.One
262
+ else:
263
+ return S.Infinity
264
+
265
+ @property
266
+ def elements(self):
267
+ """
268
+ Return the elements of the free group.
269
+
270
+ Examples
271
+ ========
272
+
273
+ >>> from sympy.combinatorics import free_group
274
+ >>> (z,) = free_group("")
275
+ >>> z.elements
276
+ {<identity>}
277
+
278
+ """
279
+ if self.rank == 0:
280
+ # A set containing Identity element of `FreeGroup` self is returned
281
+ return {self.identity}
282
+ else:
283
+ raise ValueError("Group contains infinitely many elements"
284
+ ", hence cannot be represented")
285
+
286
+ @property
287
+ def rank(self):
288
+ r"""
289
+ In group theory, the `rank` of a group `G`, denoted `G.rank`,
290
+ can refer to the smallest cardinality of a generating set
291
+ for G, that is
292
+
293
+ \operatorname{rank}(G)=\min\{ |X|: X\subseteq G, \left\langle X\right\rangle =G\}.
294
+
295
+ """
296
+ return self._rank
297
+
298
+ @property
299
+ def is_abelian(self):
300
+ """Returns if the group is Abelian.
301
+
302
+ Examples
303
+ ========
304
+
305
+ >>> from sympy.combinatorics import free_group
306
+ >>> f, x, y, z = free_group("x y z")
307
+ >>> f.is_abelian
308
+ False
309
+
310
+ """
311
+ return self.rank in (0, 1)
312
+
313
+ @property
314
+ def identity(self):
315
+ """Returns the identity element of free group."""
316
+ return self.dtype()
317
+
318
+ def contains(self, g):
319
+ """Tests if Free Group element ``g`` belong to self, ``G``.
320
+
321
+ In mathematical terms any linear combination of generators
322
+ of a Free Group is contained in it.
323
+
324
+ Examples
325
+ ========
326
+
327
+ >>> from sympy.combinatorics import free_group
328
+ >>> f, x, y, z = free_group("x y z")
329
+ >>> f.contains(x**3*y**2)
330
+ True
331
+
332
+ """
333
+ if not isinstance(g, FreeGroupElement):
334
+ return False
335
+ elif self != g.group:
336
+ return False
337
+ else:
338
+ return True
339
+
340
+ def center(self):
341
+ """Returns the center of the free group `self`."""
342
+ return {self.identity}
343
+
344
+
345
+ ############################################################################
346
+ # FreeGroupElement #
347
+ ############################################################################
348
+
349
+
350
+ class FreeGroupElement(CantSympify, DefaultPrinting, tuple):
351
+ """Used to create elements of FreeGroup. It cannot be used directly to
352
+ create a free group element. It is called by the `dtype` method of the
353
+ `FreeGroup` class.
354
+
355
+ """
356
+ __slots__ = ()
357
+ is_assoc_word = True
358
+
359
+ def new(self, init):
360
+ return self.__class__(init)
361
+
362
+ _hash = None
363
+
364
+ def __hash__(self):
365
+ _hash = self._hash
366
+ if _hash is None:
367
+ self._hash = _hash = hash((self.group, frozenset(tuple(self))))
368
+ return _hash
369
+
370
+ def copy(self):
371
+ return self.new(self)
372
+
373
+ @property
374
+ def is_identity(self):
375
+ return not self.array_form
376
+
377
+ @property
378
+ def array_form(self):
379
+ """
380
+ SymPy provides two different internal kinds of representation
381
+ of associative words. The first one is called the `array_form`
382
+ which is a tuple containing `tuples` as its elements, where the
383
+ size of each tuple is two. At the first position the tuple
384
+ contains the `symbol-generator`, while at the second position
385
+ of tuple contains the exponent of that generator at the position.
386
+ Since elements (i.e. words) do not commute, the indexing of tuple
387
+ makes that property to stay.
388
+
389
+ The structure in ``array_form`` of ``FreeGroupElement`` is of form:
390
+
391
+ ``( ( symbol_of_gen, exponent ), ( , ), ... ( , ) )``
392
+
393
+ Examples
394
+ ========
395
+
396
+ >>> from sympy.combinatorics import free_group
397
+ >>> f, x, y, z = free_group("x y z")
398
+ >>> (x*z).array_form
399
+ ((x, 1), (z, 1))
400
+ >>> (x**2*z*y*x**2).array_form
401
+ ((x, 2), (z, 1), (y, 1), (x, 2))
402
+
403
+ See Also
404
+ ========
405
+
406
+ letter_repr
407
+
408
+ """
409
+ return tuple(self)
410
+
411
+ @property
412
+ def letter_form(self):
413
+ """
414
+ The letter representation of a ``FreeGroupElement`` is a tuple
415
+ of generator symbols, with each entry corresponding to a group
416
+ generator. Inverses of the generators are represented by
417
+ negative generator symbols.
418
+
419
+ Examples
420
+ ========
421
+
422
+ >>> from sympy.combinatorics import free_group
423
+ >>> f, a, b, c, d = free_group("a b c d")
424
+ >>> (a**3).letter_form
425
+ (a, a, a)
426
+ >>> (a**2*d**-2*a*b**-4).letter_form
427
+ (a, a, -d, -d, a, -b, -b, -b, -b)
428
+ >>> (a**-2*b**3*d).letter_form
429
+ (-a, -a, b, b, b, d)
430
+
431
+ See Also
432
+ ========
433
+
434
+ array_form
435
+
436
+ """
437
+ return tuple(flatten([(i,)*j if j > 0 else (-i,)*(-j)
438
+ for i, j in self.array_form]))
439
+
440
+ def __getitem__(self, i):
441
+ group = self.group
442
+ r = self.letter_form[i]
443
+ if r.is_Symbol:
444
+ return group.dtype(((r, 1),))
445
+ else:
446
+ return group.dtype(((-r, -1),))
447
+
448
+ def index(self, gen):
449
+ if len(gen) != 1:
450
+ raise ValueError()
451
+ return (self.letter_form).index(gen.letter_form[0])
452
+
453
+ @property
454
+ def letter_form_elm(self):
455
+ """
456
+ """
457
+ group = self.group
458
+ r = self.letter_form
459
+ return [group.dtype(((elm,1),)) if elm.is_Symbol \
460
+ else group.dtype(((-elm,-1),)) for elm in r]
461
+
462
+ @property
463
+ def ext_rep(self):
464
+ """This is called the External Representation of ``FreeGroupElement``
465
+ """
466
+ return tuple(flatten(self.array_form))
467
+
468
+ def __contains__(self, gen):
469
+ return gen.array_form[0][0] in tuple([r[0] for r in self.array_form])
470
+
471
+ def __str__(self):
472
+ if self.is_identity:
473
+ return "<identity>"
474
+
475
+ str_form = ""
476
+ array_form = self.array_form
477
+ for i in range(len(array_form)):
478
+ if i == len(array_form) - 1:
479
+ if array_form[i][1] == 1:
480
+ str_form += str(array_form[i][0])
481
+ else:
482
+ str_form += str(array_form[i][0]) + \
483
+ "**" + str(array_form[i][1])
484
+ else:
485
+ if array_form[i][1] == 1:
486
+ str_form += str(array_form[i][0]) + "*"
487
+ else:
488
+ str_form += str(array_form[i][0]) + \
489
+ "**" + str(array_form[i][1]) + "*"
490
+ return str_form
491
+
492
+ __repr__ = __str__
493
+
494
+ def __pow__(self, n):
495
+ n = as_int(n)
496
+ result = self.group.identity
497
+ if n == 0:
498
+ return result
499
+ if n < 0:
500
+ n = -n
501
+ x = self.inverse()
502
+ else:
503
+ x = self
504
+ while True:
505
+ if n % 2:
506
+ result *= x
507
+ n >>= 1
508
+ if not n:
509
+ break
510
+ x *= x
511
+ return result
512
+
513
+ def __mul__(self, other):
514
+ """Returns the product of elements belonging to the same ``FreeGroup``.
515
+
516
+ Examples
517
+ ========
518
+
519
+ >>> from sympy.combinatorics import free_group
520
+ >>> f, x, y, z = free_group("x y z")
521
+ >>> x*y**2*y**-4
522
+ x*y**-2
523
+ >>> z*y**-2
524
+ z*y**-2
525
+ >>> x**2*y*y**-1*x**-2
526
+ <identity>
527
+
528
+ """
529
+ group = self.group
530
+ if not isinstance(other, group.dtype):
531
+ raise TypeError("only FreeGroup elements of same FreeGroup can "
532
+ "be multiplied")
533
+ if self.is_identity:
534
+ return other
535
+ if other.is_identity:
536
+ return self
537
+ r = list(self.array_form + other.array_form)
538
+ zero_mul_simp(r, len(self.array_form) - 1)
539
+ return group.dtype(tuple(r))
540
+
541
+ def __truediv__(self, other):
542
+ group = self.group
543
+ if not isinstance(other, group.dtype):
544
+ raise TypeError("only FreeGroup elements of same FreeGroup can "
545
+ "be multiplied")
546
+ return self*(other.inverse())
547
+
548
+ def __rtruediv__(self, other):
549
+ group = self.group
550
+ if not isinstance(other, group.dtype):
551
+ raise TypeError("only FreeGroup elements of same FreeGroup can "
552
+ "be multiplied")
553
+ return other*(self.inverse())
554
+
555
+ def __add__(self, other):
556
+ return NotImplemented
557
+
558
+ def inverse(self):
559
+ """
560
+ Returns the inverse of a ``FreeGroupElement`` element
561
+
562
+ Examples
563
+ ========
564
+
565
+ >>> from sympy.combinatorics import free_group
566
+ >>> f, x, y, z = free_group("x y z")
567
+ >>> x.inverse()
568
+ x**-1
569
+ >>> (x*y).inverse()
570
+ y**-1*x**-1
571
+
572
+ """
573
+ group = self.group
574
+ r = tuple([(i, -j) for i, j in self.array_form[::-1]])
575
+ return group.dtype(r)
576
+
577
+ def order(self):
578
+ """Find the order of a ``FreeGroupElement``.
579
+
580
+ Examples
581
+ ========
582
+
583
+ >>> from sympy.combinatorics import free_group
584
+ >>> f, x, y = free_group("x y")
585
+ >>> (x**2*y*y**-1*x**-2).order()
586
+ 1
587
+
588
+ """
589
+ if self.is_identity:
590
+ return S.One
591
+ else:
592
+ return S.Infinity
593
+
594
+ def commutator(self, other):
595
+ """
596
+ Return the commutator of `self` and `x`: ``~x*~self*x*self``
597
+
598
+ """
599
+ group = self.group
600
+ if not isinstance(other, group.dtype):
601
+ raise ValueError("commutator of only FreeGroupElement of the same "
602
+ "FreeGroup exists")
603
+ else:
604
+ return self.inverse()*other.inverse()*self*other
605
+
606
+ def eliminate_words(self, words, _all=False, inverse=True):
607
+ '''
608
+ Replace each subword from the dictionary `words` by words[subword].
609
+ If words is a list, replace the words by the identity.
610
+
611
+ '''
612
+ again = True
613
+ new = self
614
+ if isinstance(words, dict):
615
+ while again:
616
+ again = False
617
+ for sub in words:
618
+ prev = new
619
+ new = new.eliminate_word(sub, words[sub], _all=_all, inverse=inverse)
620
+ if new != prev:
621
+ again = True
622
+ else:
623
+ while again:
624
+ again = False
625
+ for sub in words:
626
+ prev = new
627
+ new = new.eliminate_word(sub, _all=_all, inverse=inverse)
628
+ if new != prev:
629
+ again = True
630
+ return new
631
+
632
+ def eliminate_word(self, gen, by=None, _all=False, inverse=True):
633
+ """
634
+ For an associative word `self`, a subword `gen`, and an associative
635
+ word `by` (identity by default), return the associative word obtained by
636
+ replacing each occurrence of `gen` in `self` by `by`. If `_all = True`,
637
+ the occurrences of `gen` that may appear after the first substitution will
638
+ also be replaced and so on until no occurrences are found. This might not
639
+ always terminate (e.g. `(x).eliminate_word(x, x**2, _all=True)`).
640
+
641
+ Examples
642
+ ========
643
+
644
+ >>> from sympy.combinatorics import free_group
645
+ >>> f, x, y = free_group("x y")
646
+ >>> w = x**5*y*x**2*y**-4*x
647
+ >>> w.eliminate_word( x, x**2 )
648
+ x**10*y*x**4*y**-4*x**2
649
+ >>> w.eliminate_word( x, y**-1 )
650
+ y**-11
651
+ >>> w.eliminate_word(x**5)
652
+ y*x**2*y**-4*x
653
+ >>> w.eliminate_word(x*y, y)
654
+ x**4*y*x**2*y**-4*x
655
+
656
+ See Also
657
+ ========
658
+ substituted_word
659
+
660
+ """
661
+ if by is None:
662
+ by = self.group.identity
663
+ if self.is_independent(gen) or gen == by:
664
+ return self
665
+ if gen == self:
666
+ return by
667
+ if gen**-1 == by:
668
+ _all = False
669
+ word = self
670
+ l = len(gen)
671
+
672
+ try:
673
+ i = word.subword_index(gen)
674
+ k = 1
675
+ except ValueError:
676
+ if not inverse:
677
+ return word
678
+ try:
679
+ i = word.subword_index(gen**-1)
680
+ k = -1
681
+ except ValueError:
682
+ return word
683
+
684
+ word = word.subword(0, i)*by**k*word.subword(i+l, len(word)).eliminate_word(gen, by)
685
+
686
+ if _all:
687
+ return word.eliminate_word(gen, by, _all=True, inverse=inverse)
688
+ else:
689
+ return word
690
+
691
+ def __len__(self):
692
+ """
693
+ For an associative word `self`, returns the number of letters in it.
694
+
695
+ Examples
696
+ ========
697
+
698
+ >>> from sympy.combinatorics import free_group
699
+ >>> f, a, b = free_group("a b")
700
+ >>> w = a**5*b*a**2*b**-4*a
701
+ >>> len(w)
702
+ 13
703
+ >>> len(a**17)
704
+ 17
705
+ >>> len(w**0)
706
+ 0
707
+
708
+ """
709
+ return sum(abs(j) for (i, j) in self)
710
+
711
+ def __eq__(self, other):
712
+ """
713
+ Two associative words are equal if they are words over the
714
+ same alphabet and if they are sequences of the same letters.
715
+ This is equivalent to saying that the external representations
716
+ of the words are equal.
717
+ There is no "universal" empty word, every alphabet has its own
718
+ empty word.
719
+
720
+ Examples
721
+ ========
722
+
723
+ >>> from sympy.combinatorics import free_group
724
+ >>> f, swapnil0, swapnil1 = free_group("swapnil0 swapnil1")
725
+ >>> f
726
+ <free group on the generators (swapnil0, swapnil1)>
727
+ >>> g, swap0, swap1 = free_group("swap0 swap1")
728
+ >>> g
729
+ <free group on the generators (swap0, swap1)>
730
+
731
+ >>> swapnil0 == swapnil1
732
+ False
733
+ >>> swapnil0*swapnil1 == swapnil1/swapnil1*swapnil0*swapnil1
734
+ True
735
+ >>> swapnil0*swapnil1 == swapnil1*swapnil0
736
+ False
737
+ >>> swapnil1**0 == swap0**0
738
+ False
739
+
740
+ """
741
+ group = self.group
742
+ if not isinstance(other, group.dtype):
743
+ return False
744
+ return tuple.__eq__(self, other)
745
+
746
+ def __lt__(self, other):
747
+ """
748
+ The ordering of associative words is defined by length and
749
+ lexicography (this ordering is called short-lex ordering), that
750
+ is, shorter words are smaller than longer words, and words of the
751
+ same length are compared w.r.t. the lexicographical ordering induced
752
+ by the ordering of generators. Generators are sorted according
753
+ to the order in which they were created. If the generators are
754
+ invertible then each generator `g` is larger than its inverse `g^{-1}`,
755
+ and `g^{-1}` is larger than every generator that is smaller than `g`.
756
+
757
+ Examples
758
+ ========
759
+
760
+ >>> from sympy.combinatorics import free_group
761
+ >>> f, a, b = free_group("a b")
762
+ >>> b < a
763
+ False
764
+ >>> a < a.inverse()
765
+ False
766
+
767
+ """
768
+ group = self.group
769
+ if not isinstance(other, group.dtype):
770
+ raise TypeError("only FreeGroup elements of same FreeGroup can "
771
+ "be compared")
772
+ l = len(self)
773
+ m = len(other)
774
+ # implement lenlex order
775
+ if l < m:
776
+ return True
777
+ elif l > m:
778
+ return False
779
+ for i in range(l):
780
+ a = self[i].array_form[0]
781
+ b = other[i].array_form[0]
782
+ p = group.symbols.index(a[0])
783
+ q = group.symbols.index(b[0])
784
+ if p < q:
785
+ return True
786
+ elif p > q:
787
+ return False
788
+ elif a[1] < b[1]:
789
+ return True
790
+ elif a[1] > b[1]:
791
+ return False
792
+ return False
793
+
794
+ def __le__(self, other):
795
+ return (self == other or self < other)
796
+
797
+ def __gt__(self, other):
798
+ """
799
+
800
+ Examples
801
+ ========
802
+
803
+ >>> from sympy.combinatorics import free_group
804
+ >>> f, x, y, z = free_group("x y z")
805
+ >>> y**2 > x**2
806
+ True
807
+ >>> y*z > z*y
808
+ False
809
+ >>> x > x.inverse()
810
+ True
811
+
812
+ """
813
+ group = self.group
814
+ if not isinstance(other, group.dtype):
815
+ raise TypeError("only FreeGroup elements of same FreeGroup can "
816
+ "be compared")
817
+ return not self <= other
818
+
819
+ def __ge__(self, other):
820
+ return not self < other
821
+
822
+ def exponent_sum(self, gen):
823
+ """
824
+ For an associative word `self` and a generator or inverse of generator
825
+ `gen`, ``exponent_sum`` returns the number of times `gen` appears in
826
+ `self` minus the number of times its inverse appears in `self`. If
827
+ neither `gen` nor its inverse occur in `self` then 0 is returned.
828
+
829
+ Examples
830
+ ========
831
+
832
+ >>> from sympy.combinatorics import free_group
833
+ >>> F, x, y = free_group("x, y")
834
+ >>> w = x**2*y**3
835
+ >>> w.exponent_sum(x)
836
+ 2
837
+ >>> w.exponent_sum(x**-1)
838
+ -2
839
+ >>> w = x**2*y**4*x**-3
840
+ >>> w.exponent_sum(x)
841
+ -1
842
+
843
+ See Also
844
+ ========
845
+
846
+ generator_count
847
+
848
+ """
849
+ if len(gen) != 1:
850
+ raise ValueError("gen must be a generator or inverse of a generator")
851
+ s = gen.array_form[0]
852
+ return s[1]*sum(i[1] for i in self.array_form if i[0] == s[0])
853
+
854
+ def generator_count(self, gen):
855
+ """
856
+ For an associative word `self` and a generator `gen`,
857
+ ``generator_count`` returns the multiplicity of generator
858
+ `gen` in `self`.
859
+
860
+ Examples
861
+ ========
862
+
863
+ >>> from sympy.combinatorics import free_group
864
+ >>> F, x, y = free_group("x, y")
865
+ >>> w = x**2*y**3
866
+ >>> w.generator_count(x)
867
+ 2
868
+ >>> w = x**2*y**4*x**-3
869
+ >>> w.generator_count(x)
870
+ 5
871
+
872
+ See Also
873
+ ========
874
+
875
+ exponent_sum
876
+
877
+ """
878
+ if len(gen) != 1 or gen.array_form[0][1] < 0:
879
+ raise ValueError("gen must be a generator")
880
+ s = gen.array_form[0]
881
+ return s[1]*sum(abs(i[1]) for i in self.array_form if i[0] == s[0])
882
+
883
+ def subword(self, from_i, to_j, strict=True):
884
+ """
885
+ For an associative word `self` and two positive integers `from_i` and
886
+ `to_j`, `subword` returns the subword of `self` that begins at position
887
+ `from_i` and ends at `to_j - 1`, indexing is done with origin 0.
888
+
889
+ Examples
890
+ ========
891
+
892
+ >>> from sympy.combinatorics import free_group
893
+ >>> f, a, b = free_group("a b")
894
+ >>> w = a**5*b*a**2*b**-4*a
895
+ >>> w.subword(2, 6)
896
+ a**3*b
897
+
898
+ """
899
+ group = self.group
900
+ if not strict:
901
+ from_i = max(from_i, 0)
902
+ to_j = min(len(self), to_j)
903
+ if from_i < 0 or to_j > len(self):
904
+ raise ValueError("`from_i`, `to_j` must be positive and no greater than "
905
+ "the length of associative word")
906
+ if to_j <= from_i:
907
+ return group.identity
908
+ else:
909
+ letter_form = self.letter_form[from_i: to_j]
910
+ array_form = letter_form_to_array_form(letter_form, group)
911
+ return group.dtype(array_form)
912
+
913
+ def subword_index(self, word, start = 0):
914
+ '''
915
+ Find the index of `word` in `self`.
916
+
917
+ Examples
918
+ ========
919
+
920
+ >>> from sympy.combinatorics import free_group
921
+ >>> f, a, b = free_group("a b")
922
+ >>> w = a**2*b*a*b**3
923
+ >>> w.subword_index(a*b*a*b)
924
+ 1
925
+
926
+ '''
927
+ l = len(word)
928
+ self_lf = self.letter_form
929
+ word_lf = word.letter_form
930
+ index = None
931
+ for i in range(start,len(self_lf)-l+1):
932
+ if self_lf[i:i+l] == word_lf:
933
+ index = i
934
+ break
935
+ if index is not None:
936
+ return index
937
+ else:
938
+ raise ValueError("The given word is not a subword of self")
939
+
940
+ def is_dependent(self, word):
941
+ """
942
+ Examples
943
+ ========
944
+
945
+ >>> from sympy.combinatorics import free_group
946
+ >>> F, x, y = free_group("x, y")
947
+ >>> (x**4*y**-3).is_dependent(x**4*y**-2)
948
+ True
949
+ >>> (x**2*y**-1).is_dependent(x*y)
950
+ False
951
+ >>> (x*y**2*x*y**2).is_dependent(x*y**2)
952
+ True
953
+ >>> (x**12).is_dependent(x**-4)
954
+ True
955
+
956
+ See Also
957
+ ========
958
+
959
+ is_independent
960
+
961
+ """
962
+ try:
963
+ return self.subword_index(word) is not None
964
+ except ValueError:
965
+ pass
966
+ try:
967
+ return self.subword_index(word**-1) is not None
968
+ except ValueError:
969
+ return False
970
+
971
+ def is_independent(self, word):
972
+ """
973
+
974
+ See Also
975
+ ========
976
+
977
+ is_dependent
978
+
979
+ """
980
+ return not self.is_dependent(word)
981
+
982
+ def contains_generators(self):
983
+ """
984
+ Examples
985
+ ========
986
+
987
+ >>> from sympy.combinatorics import free_group
988
+ >>> F, x, y, z = free_group("x, y, z")
989
+ >>> (x**2*y**-1).contains_generators()
990
+ {x, y}
991
+ >>> (x**3*z).contains_generators()
992
+ {x, z}
993
+
994
+ """
995
+ group = self.group
996
+ gens = {group.dtype(((syllable[0], 1),)) for syllable in self.array_form}
997
+ return gens
998
+
999
+ def cyclic_subword(self, from_i, to_j):
1000
+ group = self.group
1001
+ l = len(self)
1002
+ letter_form = self.letter_form
1003
+ period1 = int(from_i/l)
1004
+ if from_i >= l:
1005
+ from_i -= l*period1
1006
+ to_j -= l*period1
1007
+ diff = to_j - from_i
1008
+ word = letter_form[from_i: to_j]
1009
+ period2 = int(to_j/l) - 1
1010
+ word += letter_form*period2 + letter_form[:diff-l+from_i-l*period2]
1011
+ word = letter_form_to_array_form(word, group)
1012
+ return group.dtype(word)
1013
+
1014
+ def cyclic_conjugates(self):
1015
+ """Returns a words which are cyclic to the word `self`.
1016
+
1017
+ Examples
1018
+ ========
1019
+
1020
+ >>> from sympy.combinatorics import free_group
1021
+ >>> F, x, y = free_group("x, y")
1022
+ >>> w = x*y*x*y*x
1023
+ >>> w.cyclic_conjugates()
1024
+ {x*y*x**2*y, x**2*y*x*y, y*x*y*x**2, y*x**2*y*x, x*y*x*y*x}
1025
+ >>> s = x*y*x**2*y*x
1026
+ >>> s.cyclic_conjugates()
1027
+ {x**2*y*x**2*y, y*x**2*y*x**2, x*y*x**2*y*x}
1028
+
1029
+ References
1030
+ ==========
1031
+
1032
+ .. [1] https://planetmath.org/cyclicpermutation
1033
+
1034
+ """
1035
+ return {self.cyclic_subword(i, i+len(self)) for i in range(len(self))}
1036
+
1037
+ def is_cyclic_conjugate(self, w):
1038
+ """
1039
+ Checks whether words ``self``, ``w`` are cyclic conjugates.
1040
+
1041
+ Examples
1042
+ ========
1043
+
1044
+ >>> from sympy.combinatorics import free_group
1045
+ >>> F, x, y = free_group("x, y")
1046
+ >>> w1 = x**2*y**5
1047
+ >>> w2 = x*y**5*x
1048
+ >>> w1.is_cyclic_conjugate(w2)
1049
+ True
1050
+ >>> w3 = x**-1*y**5*x**-1
1051
+ >>> w3.is_cyclic_conjugate(w2)
1052
+ False
1053
+
1054
+ """
1055
+ l1 = len(self)
1056
+ l2 = len(w)
1057
+ if l1 != l2:
1058
+ return False
1059
+ w1 = self.identity_cyclic_reduction()
1060
+ w2 = w.identity_cyclic_reduction()
1061
+ letter1 = w1.letter_form
1062
+ letter2 = w2.letter_form
1063
+ str1 = ' '.join(map(str, letter1))
1064
+ str2 = ' '.join(map(str, letter2))
1065
+ if len(str1) != len(str2):
1066
+ return False
1067
+
1068
+ return str1 in str2 + ' ' + str2
1069
+
1070
+ def number_syllables(self):
1071
+ """Returns the number of syllables of the associative word `self`.
1072
+
1073
+ Examples
1074
+ ========
1075
+
1076
+ >>> from sympy.combinatorics import free_group
1077
+ >>> f, swapnil0, swapnil1 = free_group("swapnil0 swapnil1")
1078
+ >>> (swapnil1**3*swapnil0*swapnil1**-1).number_syllables()
1079
+ 3
1080
+
1081
+ """
1082
+ return len(self.array_form)
1083
+
1084
+ def exponent_syllable(self, i):
1085
+ """
1086
+ Returns the exponent of the `i`-th syllable of the associative word
1087
+ `self`.
1088
+
1089
+ Examples
1090
+ ========
1091
+
1092
+ >>> from sympy.combinatorics import free_group
1093
+ >>> f, a, b = free_group("a b")
1094
+ >>> w = a**5*b*a**2*b**-4*a
1095
+ >>> w.exponent_syllable( 2 )
1096
+ 2
1097
+
1098
+ """
1099
+ return self.array_form[i][1]
1100
+
1101
+ def generator_syllable(self, i):
1102
+ """
1103
+ Returns the symbol of the generator that is involved in the
1104
+ i-th syllable of the associative word `self`.
1105
+
1106
+ Examples
1107
+ ========
1108
+
1109
+ >>> from sympy.combinatorics import free_group
1110
+ >>> f, a, b = free_group("a b")
1111
+ >>> w = a**5*b*a**2*b**-4*a
1112
+ >>> w.generator_syllable( 3 )
1113
+ b
1114
+
1115
+ """
1116
+ return self.array_form[i][0]
1117
+
1118
+ def sub_syllables(self, from_i, to_j):
1119
+ """
1120
+ `sub_syllables` returns the subword of the associative word `self` that
1121
+ consists of syllables from positions `from_to` to `to_j`, where
1122
+ `from_to` and `to_j` must be positive integers and indexing is done
1123
+ with origin 0.
1124
+
1125
+ Examples
1126
+ ========
1127
+
1128
+ >>> from sympy.combinatorics import free_group
1129
+ >>> f, a, b = free_group("a, b")
1130
+ >>> w = a**5*b*a**2*b**-4*a
1131
+ >>> w.sub_syllables(1, 2)
1132
+ b
1133
+ >>> w.sub_syllables(3, 3)
1134
+ <identity>
1135
+
1136
+ """
1137
+ if not isinstance(from_i, int) or not isinstance(to_j, int):
1138
+ raise ValueError("both arguments should be integers")
1139
+ group = self.group
1140
+ if to_j <= from_i:
1141
+ return group.identity
1142
+ else:
1143
+ r = tuple(self.array_form[from_i: to_j])
1144
+ return group.dtype(r)
1145
+
1146
+ def substituted_word(self, from_i, to_j, by):
1147
+ """
1148
+ Returns the associative word obtained by replacing the subword of
1149
+ `self` that begins at position `from_i` and ends at position `to_j - 1`
1150
+ by the associative word `by`. `from_i` and `to_j` must be positive
1151
+ integers, indexing is done with origin 0. In other words,
1152
+ `w.substituted_word(w, from_i, to_j, by)` is the product of the three
1153
+ words: `w.subword(0, from_i)`, `by`, and
1154
+ `w.subword(to_j len(w))`.
1155
+
1156
+ See Also
1157
+ ========
1158
+
1159
+ eliminate_word
1160
+
1161
+ """
1162
+ lw = len(self)
1163
+ if from_i >= to_j or from_i > lw or to_j > lw:
1164
+ raise ValueError("values should be within bounds")
1165
+
1166
+ # otherwise there are four possibilities
1167
+
1168
+ # first if from=1 and to=lw then
1169
+ if from_i == 0 and to_j == lw:
1170
+ return by
1171
+ elif from_i == 0: # second if from_i=1 (and to_j < lw) then
1172
+ return by*self.subword(to_j, lw)
1173
+ elif to_j == lw: # third if to_j=1 (and from_i > 1) then
1174
+ return self.subword(0, from_i)*by
1175
+ else: # finally
1176
+ return self.subword(0, from_i)*by*self.subword(to_j, lw)
1177
+
1178
+ def is_cyclically_reduced(self):
1179
+ r"""Returns whether the word is cyclically reduced or not.
1180
+ A word is cyclically reduced if by forming the cycle of the
1181
+ word, the word is not reduced, i.e a word w = `a_1 ... a_n`
1182
+ is called cyclically reduced if `a_1 \ne a_n^{-1}`.
1183
+
1184
+ Examples
1185
+ ========
1186
+
1187
+ >>> from sympy.combinatorics import free_group
1188
+ >>> F, x, y = free_group("x, y")
1189
+ >>> (x**2*y**-1*x**-1).is_cyclically_reduced()
1190
+ False
1191
+ >>> (y*x**2*y**2).is_cyclically_reduced()
1192
+ True
1193
+
1194
+ """
1195
+ if not self:
1196
+ return True
1197
+ return self[0] != self[-1]**-1
1198
+
1199
+ def identity_cyclic_reduction(self):
1200
+ """Return a unique cyclically reduced version of the word.
1201
+
1202
+ Examples
1203
+ ========
1204
+
1205
+ >>> from sympy.combinatorics import free_group
1206
+ >>> F, x, y = free_group("x, y")
1207
+ >>> (x**2*y**2*x**-1).identity_cyclic_reduction()
1208
+ x*y**2
1209
+ >>> (x**-3*y**-1*x**5).identity_cyclic_reduction()
1210
+ x**2*y**-1
1211
+
1212
+ References
1213
+ ==========
1214
+
1215
+ .. [1] https://planetmath.org/cyclicallyreduced
1216
+
1217
+ """
1218
+ word = self.copy()
1219
+ group = self.group
1220
+ while not word.is_cyclically_reduced():
1221
+ exp1 = word.exponent_syllable(0)
1222
+ exp2 = word.exponent_syllable(-1)
1223
+ r = exp1 + exp2
1224
+ if r == 0:
1225
+ rep = word.array_form[1: word.number_syllables() - 1]
1226
+ else:
1227
+ rep = ((word.generator_syllable(0), exp1 + exp2),) + \
1228
+ word.array_form[1: word.number_syllables() - 1]
1229
+ word = group.dtype(rep)
1230
+ return word
1231
+
1232
+ def cyclic_reduction(self, removed=False):
1233
+ """Return a cyclically reduced version of the word. Unlike
1234
+ `identity_cyclic_reduction`, this will not cyclically permute
1235
+ the reduced word - just remove the "unreduced" bits on either
1236
+ side of it. Compare the examples with those of
1237
+ `identity_cyclic_reduction`.
1238
+
1239
+ When `removed` is `True`, return a tuple `(word, r)` where
1240
+ self `r` is such that before the reduction the word was either
1241
+ `r*word*r**-1`.
1242
+
1243
+ Examples
1244
+ ========
1245
+
1246
+ >>> from sympy.combinatorics import free_group
1247
+ >>> F, x, y = free_group("x, y")
1248
+ >>> (x**2*y**2*x**-1).cyclic_reduction()
1249
+ x*y**2
1250
+ >>> (x**-3*y**-1*x**5).cyclic_reduction()
1251
+ y**-1*x**2
1252
+ >>> (x**-3*y**-1*x**5).cyclic_reduction(removed=True)
1253
+ (y**-1*x**2, x**-3)
1254
+
1255
+ """
1256
+ word = self.copy()
1257
+ g = self.group.identity
1258
+ while not word.is_cyclically_reduced():
1259
+ exp1 = abs(word.exponent_syllable(0))
1260
+ exp2 = abs(word.exponent_syllable(-1))
1261
+ exp = min(exp1, exp2)
1262
+ start = word[0]**abs(exp)
1263
+ end = word[-1]**abs(exp)
1264
+ word = start**-1*word*end**-1
1265
+ g = g*start
1266
+ if removed:
1267
+ return word, g
1268
+ return word
1269
+
1270
+ def power_of(self, other):
1271
+ '''
1272
+ Check if `self == other**n` for some integer n.
1273
+
1274
+ Examples
1275
+ ========
1276
+
1277
+ >>> from sympy.combinatorics import free_group
1278
+ >>> F, x, y = free_group("x, y")
1279
+ >>> ((x*y)**2).power_of(x*y)
1280
+ True
1281
+ >>> (x**-3*y**-2*x**3).power_of(x**-3*y*x**3)
1282
+ True
1283
+
1284
+ '''
1285
+ if self.is_identity:
1286
+ return True
1287
+
1288
+ l = len(other)
1289
+ if l == 1:
1290
+ # self has to be a power of one generator
1291
+ gens = self.contains_generators()
1292
+ s = other in gens or other**-1 in gens
1293
+ return len(gens) == 1 and s
1294
+
1295
+ # if self is not cyclically reduced and it is a power of other,
1296
+ # other isn't cyclically reduced and the parts removed during
1297
+ # their reduction must be equal
1298
+ reduced, r1 = self.cyclic_reduction(removed=True)
1299
+ if not r1.is_identity:
1300
+ other, r2 = other.cyclic_reduction(removed=True)
1301
+ if r1 == r2:
1302
+ return reduced.power_of(other)
1303
+ return False
1304
+
1305
+ if len(self) < l or len(self) % l:
1306
+ return False
1307
+
1308
+ prefix = self.subword(0, l)
1309
+ if prefix == other or prefix**-1 == other:
1310
+ rest = self.subword(l, len(self))
1311
+ return rest.power_of(other)
1312
+ return False
1313
+
1314
+
1315
+ def letter_form_to_array_form(array_form, group):
1316
+ """
1317
+ This method converts a list given with possible repetitions of elements in
1318
+ it. It returns a new list such that repetitions of consecutive elements is
1319
+ removed and replace with a tuple element of size two such that the first
1320
+ index contains `value` and the second index contains the number of
1321
+ consecutive repetitions of `value`.
1322
+
1323
+ """
1324
+ a = list(array_form[:])
1325
+ new_array = []
1326
+ n = 1
1327
+ symbols = group.symbols
1328
+ for i in range(len(a)):
1329
+ if i == len(a) - 1:
1330
+ if a[i] == a[i - 1]:
1331
+ if (-a[i]) in symbols:
1332
+ new_array.append((-a[i], -n))
1333
+ else:
1334
+ new_array.append((a[i], n))
1335
+ else:
1336
+ if (-a[i]) in symbols:
1337
+ new_array.append((-a[i], -1))
1338
+ else:
1339
+ new_array.append((a[i], 1))
1340
+ return new_array
1341
+ elif a[i] == a[i + 1]:
1342
+ n += 1
1343
+ else:
1344
+ if (-a[i]) in symbols:
1345
+ new_array.append((-a[i], -n))
1346
+ else:
1347
+ new_array.append((a[i], n))
1348
+ n = 1
1349
+
1350
+
1351
+ def zero_mul_simp(l, index):
1352
+ """Used to combine two reduced words."""
1353
+ while index >=0 and index < len(l) - 1 and l[index][0] == l[index + 1][0]:
1354
+ exp = l[index][1] + l[index + 1][1]
1355
+ base = l[index][0]
1356
+ l[index] = (base, exp)
1357
+ del l[index + 1]
1358
+ if l[index][1] == 0:
1359
+ del l[index]
1360
+ index -= 1
.venv/lib/python3.13/site-packages/sympy/combinatorics/galois.py ADDED
@@ -0,0 +1,611 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ r"""
2
+ Construct transitive subgroups of symmetric groups, useful in Galois theory.
3
+
4
+ Besides constructing instances of the :py:class:`~.PermutationGroup` class to
5
+ represent the transitive subgroups of $S_n$ for small $n$, this module provides
6
+ *names* for these groups.
7
+
8
+ In some applications, it may be preferable to know the name of a group,
9
+ rather than receive an instance of the :py:class:`~.PermutationGroup`
10
+ class, and then have to do extra work to determine which group it is, by
11
+ checking various properties.
12
+
13
+ Names are instances of ``Enum`` classes defined in this module. With a name in
14
+ hand, the name's ``get_perm_group`` method can then be used to retrieve a
15
+ :py:class:`~.PermutationGroup`.
16
+
17
+ The names used for groups in this module are taken from [1].
18
+
19
+ References
20
+ ==========
21
+
22
+ .. [1] Cohen, H. *A Course in Computational Algebraic Number Theory*.
23
+
24
+ """
25
+
26
+ from collections import defaultdict
27
+ from enum import Enum
28
+ import itertools
29
+
30
+ from sympy.combinatorics.named_groups import (
31
+ SymmetricGroup, AlternatingGroup, CyclicGroup, DihedralGroup,
32
+ set_symmetric_group_properties, set_alternating_group_properties,
33
+ )
34
+ from sympy.combinatorics.perm_groups import PermutationGroup
35
+ from sympy.combinatorics.permutations import Permutation
36
+
37
+
38
+ class S1TransitiveSubgroups(Enum):
39
+ """
40
+ Names for the transitive subgroups of S1.
41
+ """
42
+ S1 = "S1"
43
+
44
+ def get_perm_group(self):
45
+ return SymmetricGroup(1)
46
+
47
+
48
+ class S2TransitiveSubgroups(Enum):
49
+ """
50
+ Names for the transitive subgroups of S2.
51
+ """
52
+ S2 = "S2"
53
+
54
+ def get_perm_group(self):
55
+ return SymmetricGroup(2)
56
+
57
+
58
+ class S3TransitiveSubgroups(Enum):
59
+ """
60
+ Names for the transitive subgroups of S3.
61
+ """
62
+ A3 = "A3"
63
+ S3 = "S3"
64
+
65
+ def get_perm_group(self):
66
+ if self == S3TransitiveSubgroups.A3:
67
+ return AlternatingGroup(3)
68
+ elif self == S3TransitiveSubgroups.S3:
69
+ return SymmetricGroup(3)
70
+
71
+
72
+ class S4TransitiveSubgroups(Enum):
73
+ """
74
+ Names for the transitive subgroups of S4.
75
+ """
76
+ C4 = "C4"
77
+ V = "V"
78
+ D4 = "D4"
79
+ A4 = "A4"
80
+ S4 = "S4"
81
+
82
+ def get_perm_group(self):
83
+ if self == S4TransitiveSubgroups.C4:
84
+ return CyclicGroup(4)
85
+ elif self == S4TransitiveSubgroups.V:
86
+ return four_group()
87
+ elif self == S4TransitiveSubgroups.D4:
88
+ return DihedralGroup(4)
89
+ elif self == S4TransitiveSubgroups.A4:
90
+ return AlternatingGroup(4)
91
+ elif self == S4TransitiveSubgroups.S4:
92
+ return SymmetricGroup(4)
93
+
94
+
95
+ class S5TransitiveSubgroups(Enum):
96
+ """
97
+ Names for the transitive subgroups of S5.
98
+ """
99
+ C5 = "C5"
100
+ D5 = "D5"
101
+ M20 = "M20"
102
+ A5 = "A5"
103
+ S5 = "S5"
104
+
105
+ def get_perm_group(self):
106
+ if self == S5TransitiveSubgroups.C5:
107
+ return CyclicGroup(5)
108
+ elif self == S5TransitiveSubgroups.D5:
109
+ return DihedralGroup(5)
110
+ elif self == S5TransitiveSubgroups.M20:
111
+ return M20()
112
+ elif self == S5TransitiveSubgroups.A5:
113
+ return AlternatingGroup(5)
114
+ elif self == S5TransitiveSubgroups.S5:
115
+ return SymmetricGroup(5)
116
+
117
+
118
+ class S6TransitiveSubgroups(Enum):
119
+ """
120
+ Names for the transitive subgroups of S6.
121
+ """
122
+ C6 = "C6"
123
+ S3 = "S3"
124
+ D6 = "D6"
125
+ A4 = "A4"
126
+ G18 = "G18"
127
+ A4xC2 = "A4 x C2"
128
+ S4m = "S4-"
129
+ S4p = "S4+"
130
+ G36m = "G36-"
131
+ G36p = "G36+"
132
+ S4xC2 = "S4 x C2"
133
+ PSL2F5 = "PSL2(F5)"
134
+ G72 = "G72"
135
+ PGL2F5 = "PGL2(F5)"
136
+ A6 = "A6"
137
+ S6 = "S6"
138
+
139
+ def get_perm_group(self):
140
+ if self == S6TransitiveSubgroups.C6:
141
+ return CyclicGroup(6)
142
+ elif self == S6TransitiveSubgroups.S3:
143
+ return S3_in_S6()
144
+ elif self == S6TransitiveSubgroups.D6:
145
+ return DihedralGroup(6)
146
+ elif self == S6TransitiveSubgroups.A4:
147
+ return A4_in_S6()
148
+ elif self == S6TransitiveSubgroups.G18:
149
+ return G18()
150
+ elif self == S6TransitiveSubgroups.A4xC2:
151
+ return A4xC2()
152
+ elif self == S6TransitiveSubgroups.S4m:
153
+ return S4m()
154
+ elif self == S6TransitiveSubgroups.S4p:
155
+ return S4p()
156
+ elif self == S6TransitiveSubgroups.G36m:
157
+ return G36m()
158
+ elif self == S6TransitiveSubgroups.G36p:
159
+ return G36p()
160
+ elif self == S6TransitiveSubgroups.S4xC2:
161
+ return S4xC2()
162
+ elif self == S6TransitiveSubgroups.PSL2F5:
163
+ return PSL2F5()
164
+ elif self == S6TransitiveSubgroups.G72:
165
+ return G72()
166
+ elif self == S6TransitiveSubgroups.PGL2F5:
167
+ return PGL2F5()
168
+ elif self == S6TransitiveSubgroups.A6:
169
+ return AlternatingGroup(6)
170
+ elif self == S6TransitiveSubgroups.S6:
171
+ return SymmetricGroup(6)
172
+
173
+
174
+ def four_group():
175
+ """
176
+ Return a representation of the Klein four-group as a transitive subgroup
177
+ of S4.
178
+ """
179
+ return PermutationGroup(
180
+ Permutation(0, 1)(2, 3),
181
+ Permutation(0, 2)(1, 3)
182
+ )
183
+
184
+
185
+ def M20():
186
+ """
187
+ Return a representation of the metacyclic group M20, a transitive subgroup
188
+ of S5 that is one of the possible Galois groups for polys of degree 5.
189
+
190
+ Notes
191
+ =====
192
+
193
+ See [1], Page 323.
194
+
195
+ """
196
+ G = PermutationGroup(Permutation(0, 1, 2, 3, 4), Permutation(1, 2, 4, 3))
197
+ G._degree = 5
198
+ G._order = 20
199
+ G._is_transitive = True
200
+ G._is_sym = False
201
+ G._is_alt = False
202
+ G._is_cyclic = False
203
+ G._is_dihedral = False
204
+ return G
205
+
206
+
207
+ def S3_in_S6():
208
+ """
209
+ Return a representation of S3 as a transitive subgroup of S6.
210
+
211
+ Notes
212
+ =====
213
+
214
+ The representation is found by viewing the group as the symmetries of a
215
+ triangular prism.
216
+
217
+ """
218
+ G = PermutationGroup(Permutation(0, 1, 2)(3, 4, 5), Permutation(0, 3)(2, 4)(1, 5))
219
+ set_symmetric_group_properties(G, 3, 6)
220
+ return G
221
+
222
+
223
+ def A4_in_S6():
224
+ """
225
+ Return a representation of A4 as a transitive subgroup of S6.
226
+
227
+ Notes
228
+ =====
229
+
230
+ This was computed using :py:func:`~.find_transitive_subgroups_of_S6`.
231
+
232
+ """
233
+ G = PermutationGroup(Permutation(0, 4, 5)(1, 3, 2), Permutation(0, 1, 2)(3, 5, 4))
234
+ set_alternating_group_properties(G, 4, 6)
235
+ return G
236
+
237
+
238
+ def S4m():
239
+ """
240
+ Return a representation of the S4- transitive subgroup of S6.
241
+
242
+ Notes
243
+ =====
244
+
245
+ This was computed using :py:func:`~.find_transitive_subgroups_of_S6`.
246
+
247
+ """
248
+ G = PermutationGroup(Permutation(1, 4, 5, 3), Permutation(0, 4)(1, 5)(2, 3))
249
+ set_symmetric_group_properties(G, 4, 6)
250
+ return G
251
+
252
+
253
+ def S4p():
254
+ """
255
+ Return a representation of the S4+ transitive subgroup of S6.
256
+
257
+ Notes
258
+ =====
259
+
260
+ This was computed using :py:func:`~.find_transitive_subgroups_of_S6`.
261
+
262
+ """
263
+ G = PermutationGroup(Permutation(0, 2, 4, 1)(3, 5), Permutation(0, 3)(4, 5))
264
+ set_symmetric_group_properties(G, 4, 6)
265
+ return G
266
+
267
+
268
+ def A4xC2():
269
+ """
270
+ Return a representation of the (A4 x C2) transitive subgroup of S6.
271
+
272
+ Notes
273
+ =====
274
+
275
+ This was computed using :py:func:`~.find_transitive_subgroups_of_S6`.
276
+
277
+ """
278
+ return PermutationGroup(
279
+ Permutation(0, 4, 5)(1, 3, 2), Permutation(0, 1, 2)(3, 5, 4),
280
+ Permutation(5)(2, 4))
281
+
282
+
283
+ def S4xC2():
284
+ """
285
+ Return a representation of the (S4 x C2) transitive subgroup of S6.
286
+
287
+ Notes
288
+ =====
289
+
290
+ This was computed using :py:func:`~.find_transitive_subgroups_of_S6`.
291
+
292
+ """
293
+ return PermutationGroup(
294
+ Permutation(1, 4, 5, 3), Permutation(0, 4)(1, 5)(2, 3),
295
+ Permutation(1, 4)(3, 5))
296
+
297
+
298
+ def G18():
299
+ """
300
+ Return a representation of the group G18, a transitive subgroup of S6
301
+ isomorphic to the semidirect product of C3^2 with C2.
302
+
303
+ Notes
304
+ =====
305
+
306
+ This was computed using :py:func:`~.find_transitive_subgroups_of_S6`.
307
+
308
+ """
309
+ return PermutationGroup(
310
+ Permutation(5)(0, 1, 2), Permutation(3, 4, 5),
311
+ Permutation(0, 4)(1, 5)(2, 3))
312
+
313
+
314
+ def G36m():
315
+ """
316
+ Return a representation of the group G36-, a transitive subgroup of S6
317
+ isomorphic to the semidirect product of C3^2 with C2^2.
318
+
319
+ Notes
320
+ =====
321
+
322
+ This was computed using :py:func:`~.find_transitive_subgroups_of_S6`.
323
+
324
+ """
325
+ return PermutationGroup(
326
+ Permutation(5)(0, 1, 2), Permutation(3, 4, 5),
327
+ Permutation(1, 2)(3, 5), Permutation(0, 4)(1, 5)(2, 3))
328
+
329
+
330
+ def G36p():
331
+ """
332
+ Return a representation of the group G36+, a transitive subgroup of S6
333
+ isomorphic to the semidirect product of C3^2 with C4.
334
+
335
+ Notes
336
+ =====
337
+
338
+ This was computed using :py:func:`~.find_transitive_subgroups_of_S6`.
339
+
340
+ """
341
+ return PermutationGroup(
342
+ Permutation(5)(0, 1, 2), Permutation(3, 4, 5),
343
+ Permutation(0, 5, 2, 3)(1, 4))
344
+
345
+
346
+ def G72():
347
+ """
348
+ Return a representation of the group G72, a transitive subgroup of S6
349
+ isomorphic to the semidirect product of C3^2 with D4.
350
+
351
+ Notes
352
+ =====
353
+
354
+ See [1], Page 325.
355
+
356
+ """
357
+ return PermutationGroup(
358
+ Permutation(5)(0, 1, 2),
359
+ Permutation(0, 4, 1, 3)(2, 5), Permutation(0, 3)(1, 4)(2, 5))
360
+
361
+
362
+ def PSL2F5():
363
+ r"""
364
+ Return a representation of the group $PSL_2(\mathbb{F}_5)$, as a transitive
365
+ subgroup of S6, isomorphic to $A_5$.
366
+
367
+ Notes
368
+ =====
369
+
370
+ This was computed using :py:func:`~.find_transitive_subgroups_of_S6`.
371
+
372
+ """
373
+ G = PermutationGroup(
374
+ Permutation(0, 4, 5)(1, 3, 2), Permutation(0, 4, 3, 1, 5))
375
+ set_alternating_group_properties(G, 5, 6)
376
+ return G
377
+
378
+
379
+ def PGL2F5():
380
+ r"""
381
+ Return a representation of the group $PGL_2(\mathbb{F}_5)$, as a transitive
382
+ subgroup of S6, isomorphic to $S_5$.
383
+
384
+ Notes
385
+ =====
386
+
387
+ See [1], Page 325.
388
+
389
+ """
390
+ G = PermutationGroup(
391
+ Permutation(0, 1, 2, 3, 4), Permutation(0, 5)(1, 2)(3, 4))
392
+ set_symmetric_group_properties(G, 5, 6)
393
+ return G
394
+
395
+
396
+ def find_transitive_subgroups_of_S6(*targets, print_report=False):
397
+ r"""
398
+ Search for certain transitive subgroups of $S_6$.
399
+
400
+ The symmetric group $S_6$ has 16 different transitive subgroups, up to
401
+ conjugacy. Some are more easily constructed than others. For example, the
402
+ dihedral group $D_6$ is immediately found, but it is not at all obvious how
403
+ to realize $S_4$ or $S_5$ *transitively* within $S_6$.
404
+
405
+ In some cases there are well-known constructions that can be used. For
406
+ example, $S_5$ is isomorphic to $PGL_2(\mathbb{F}_5)$, which acts in a
407
+ natural way on the projective line $P^1(\mathbb{F}_5)$, a set of order 6.
408
+
409
+ In absence of such special constructions however, we can simply search for
410
+ generators. For example, transitive instances of $A_4$ and $S_4$ can be
411
+ found within $S_6$ in this way.
412
+
413
+ Once we are engaged in such searches, it may then be easier (if less
414
+ elegant) to find even those groups like $S_5$ that do have special
415
+ constructions, by mere search.
416
+
417
+ This function locates generators for transitive instances in $S_6$ of the
418
+ following subgroups:
419
+
420
+ * $A_4$
421
+ * $S_4^-$ ($S_4$ not contained within $A_6$)
422
+ * $S_4^+$ ($S_4$ contained within $A_6$)
423
+ * $A_4 \times C_2$
424
+ * $S_4 \times C_2$
425
+ * $G_{18} = C_3^2 \rtimes C_2$
426
+ * $G_{36}^- = C_3^2 \rtimes C_2^2$
427
+ * $G_{36}^+ = C_3^2 \rtimes C_4$
428
+ * $G_{72} = C_3^2 \rtimes D_4$
429
+ * $A_5$
430
+ * $S_5$
431
+
432
+ Note: Each of these groups also has a dedicated function in this module
433
+ that returns the group immediately, using generators that were found by
434
+ this search procedure.
435
+
436
+ The search procedure serves as a record of how these generators were
437
+ found. Also, due to randomness in the generation of the elements of
438
+ permutation groups, it can be called again, in order to (probably) get
439
+ different generators for the same groups.
440
+
441
+ Parameters
442
+ ==========
443
+
444
+ targets : list of :py:class:`~.S6TransitiveSubgroups` values
445
+ The groups you want to find.
446
+
447
+ print_report : bool (default False)
448
+ If True, print to stdout the generators found for each group.
449
+
450
+ Returns
451
+ =======
452
+
453
+ dict
454
+ mapping each name in *targets* to the :py:class:`~.PermutationGroup`
455
+ that was found
456
+
457
+ References
458
+ ==========
459
+
460
+ .. [2] https://en.wikipedia.org/wiki/Projective_linear_group#Exceptional_isomorphisms
461
+ .. [3] https://en.wikipedia.org/wiki/Automorphisms_of_the_symmetric_and_alternating_groups#PGL%282,5%29
462
+
463
+ """
464
+ def elts_by_order(G):
465
+ """Sort the elements of a group by their order. """
466
+ elts = defaultdict(list)
467
+ for g in G.elements:
468
+ elts[g.order()].append(g)
469
+ return elts
470
+
471
+ def order_profile(G, name=None):
472
+ """Determine how many elements a group has, of each order. """
473
+ elts = elts_by_order(G)
474
+ profile = {o:len(e) for o, e in elts.items()}
475
+ if name:
476
+ print(f'{name}: ' + ' '.join(f'{len(profile[r])}@{r}' for r in sorted(profile.keys())))
477
+ return profile
478
+
479
+ S6 = SymmetricGroup(6)
480
+ A6 = AlternatingGroup(6)
481
+ S6_by_order = elts_by_order(S6)
482
+
483
+ def search(existing_gens, needed_gen_orders, order, alt=None, profile=None, anti_profile=None):
484
+ """
485
+ Find a transitive subgroup of S6.
486
+
487
+ Parameters
488
+ ==========
489
+
490
+ existing_gens : list of Permutation
491
+ Optionally empty list of generators that must be in the group.
492
+
493
+ needed_gen_orders : list of positive int
494
+ Nonempty list of the orders of the additional generators that are
495
+ to be found.
496
+
497
+ order: int
498
+ The order of the group being sought.
499
+
500
+ alt: bool, None
501
+ If True, require the group to be contained in A6.
502
+ If False, require the group not to be contained in A6.
503
+
504
+ profile : dict
505
+ If given, the group's order profile must equal this.
506
+
507
+ anti_profile : dict
508
+ If given, the group's order profile must *not* equal this.
509
+
510
+ """
511
+ for gens in itertools.product(*[S6_by_order[n] for n in needed_gen_orders]):
512
+ if len(set(gens)) < len(gens):
513
+ continue
514
+ G = PermutationGroup(existing_gens + list(gens))
515
+ if G.order() == order and G.is_transitive():
516
+ if alt is not None and G.is_subgroup(A6) != alt:
517
+ continue
518
+ if profile and order_profile(G) != profile:
519
+ continue
520
+ if anti_profile and order_profile(G) == anti_profile:
521
+ continue
522
+ return G
523
+
524
+ def match_known_group(G, alt=None):
525
+ needed = [g.order() for g in G.generators]
526
+ return search([], needed, G.order(), alt=alt, profile=order_profile(G))
527
+
528
+ found = {}
529
+
530
+ def finish_up(name, G):
531
+ found[name] = G
532
+ if print_report:
533
+ print("=" * 40)
534
+ print(f"{name}:")
535
+ print(G.generators)
536
+
537
+ if S6TransitiveSubgroups.A4 in targets or S6TransitiveSubgroups.A4xC2 in targets:
538
+ A4_in_S6 = match_known_group(AlternatingGroup(4))
539
+ finish_up(S6TransitiveSubgroups.A4, A4_in_S6)
540
+
541
+ if S6TransitiveSubgroups.S4m in targets or S6TransitiveSubgroups.S4xC2 in targets:
542
+ S4m_in_S6 = match_known_group(SymmetricGroup(4), alt=False)
543
+ finish_up(S6TransitiveSubgroups.S4m, S4m_in_S6)
544
+
545
+ if S6TransitiveSubgroups.S4p in targets:
546
+ S4p_in_S6 = match_known_group(SymmetricGroup(4), alt=True)
547
+ finish_up(S6TransitiveSubgroups.S4p, S4p_in_S6)
548
+
549
+ if S6TransitiveSubgroups.A4xC2 in targets:
550
+ A4xC2_in_S6 = search(A4_in_S6.generators, [2], 24, anti_profile=order_profile(SymmetricGroup(4)))
551
+ finish_up(S6TransitiveSubgroups.A4xC2, A4xC2_in_S6)
552
+
553
+ if S6TransitiveSubgroups.S4xC2 in targets:
554
+ S4xC2_in_S6 = search(S4m_in_S6.generators, [2], 48)
555
+ finish_up(S6TransitiveSubgroups.S4xC2, S4xC2_in_S6)
556
+
557
+ # For the normal factor N = C3^2 in any of the G_n subgroups, we take one
558
+ # obvious instance of C3^2 in S6:
559
+ N_gens = [Permutation(5)(0, 1, 2), Permutation(5)(3, 4, 5)]
560
+
561
+ if S6TransitiveSubgroups.G18 in targets:
562
+ G18_in_S6 = search(N_gens, [2], 18)
563
+ finish_up(S6TransitiveSubgroups.G18, G18_in_S6)
564
+
565
+ if S6TransitiveSubgroups.G36m in targets:
566
+ G36m_in_S6 = search(N_gens, [2, 2], 36, alt=False)
567
+ finish_up(S6TransitiveSubgroups.G36m, G36m_in_S6)
568
+
569
+ if S6TransitiveSubgroups.G36p in targets:
570
+ G36p_in_S6 = search(N_gens, [4], 36, alt=True)
571
+ finish_up(S6TransitiveSubgroups.G36p, G36p_in_S6)
572
+
573
+ if S6TransitiveSubgroups.G72 in targets:
574
+ G72_in_S6 = search(N_gens, [4, 2], 72)
575
+ finish_up(S6TransitiveSubgroups.G72, G72_in_S6)
576
+
577
+ # The PSL2(F5) and PGL2(F5) subgroups are isomorphic to A5 and S5, resp.
578
+
579
+ if S6TransitiveSubgroups.PSL2F5 in targets:
580
+ PSL2F5_in_S6 = match_known_group(AlternatingGroup(5))
581
+ finish_up(S6TransitiveSubgroups.PSL2F5, PSL2F5_in_S6)
582
+
583
+ if S6TransitiveSubgroups.PGL2F5 in targets:
584
+ PGL2F5_in_S6 = match_known_group(SymmetricGroup(5))
585
+ finish_up(S6TransitiveSubgroups.PGL2F5, PGL2F5_in_S6)
586
+
587
+ # There is little need to "search" for any of the groups C6, S3, D6, A6,
588
+ # or S6, since they all have obvious realizations within S6. However, we
589
+ # support them here just in case a random representation is desired.
590
+
591
+ if S6TransitiveSubgroups.C6 in targets:
592
+ C6 = match_known_group(CyclicGroup(6))
593
+ finish_up(S6TransitiveSubgroups.C6, C6)
594
+
595
+ if S6TransitiveSubgroups.S3 in targets:
596
+ S3 = match_known_group(SymmetricGroup(3))
597
+ finish_up(S6TransitiveSubgroups.S3, S3)
598
+
599
+ if S6TransitiveSubgroups.D6 in targets:
600
+ D6 = match_known_group(DihedralGroup(6))
601
+ finish_up(S6TransitiveSubgroups.D6, D6)
602
+
603
+ if S6TransitiveSubgroups.A6 in targets:
604
+ A6 = match_known_group(A6)
605
+ finish_up(S6TransitiveSubgroups.A6, A6)
606
+
607
+ if S6TransitiveSubgroups.S6 in targets:
608
+ S6 = match_known_group(S6)
609
+ finish_up(S6TransitiveSubgroups.S6, S6)
610
+
611
+ return found
.venv/lib/python3.13/site-packages/sympy/combinatorics/generators.py ADDED
@@ -0,0 +1,301 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sympy.combinatorics.permutations import Permutation
2
+ from sympy.core.symbol import symbols
3
+ from sympy.matrices import Matrix
4
+ from sympy.utilities.iterables import variations, rotate_left
5
+
6
+
7
+ def symmetric(n):
8
+ """
9
+ Generates the symmetric group of order n, Sn.
10
+
11
+ Examples
12
+ ========
13
+
14
+ >>> from sympy.combinatorics.generators import symmetric
15
+ >>> list(symmetric(3))
16
+ [(2), (1 2), (2)(0 1), (0 1 2), (0 2 1), (0 2)]
17
+ """
18
+ yield from (Permutation(perm) for perm in variations(range(n), n))
19
+
20
+
21
+ def cyclic(n):
22
+ """
23
+ Generates the cyclic group of order n, Cn.
24
+
25
+ Examples
26
+ ========
27
+
28
+ >>> from sympy.combinatorics.generators import cyclic
29
+ >>> list(cyclic(5))
30
+ [(4), (0 1 2 3 4), (0 2 4 1 3),
31
+ (0 3 1 4 2), (0 4 3 2 1)]
32
+
33
+ See Also
34
+ ========
35
+
36
+ dihedral
37
+ """
38
+ gen = list(range(n))
39
+ for i in range(n):
40
+ yield Permutation(gen)
41
+ gen = rotate_left(gen, 1)
42
+
43
+
44
+ def alternating(n):
45
+ """
46
+ Generates the alternating group of order n, An.
47
+
48
+ Examples
49
+ ========
50
+
51
+ >>> from sympy.combinatorics.generators import alternating
52
+ >>> list(alternating(3))
53
+ [(2), (0 1 2), (0 2 1)]
54
+ """
55
+ for perm in variations(range(n), n):
56
+ p = Permutation(perm)
57
+ if p.is_even:
58
+ yield p
59
+
60
+
61
+ def dihedral(n):
62
+ """
63
+ Generates the dihedral group of order 2n, Dn.
64
+
65
+ The result is given as a subgroup of Sn, except for the special cases n=1
66
+ (the group S2) and n=2 (the Klein 4-group) where that's not possible
67
+ and embeddings in S2 and S4 respectively are given.
68
+
69
+ Examples
70
+ ========
71
+
72
+ >>> from sympy.combinatorics.generators import dihedral
73
+ >>> list(dihedral(3))
74
+ [(2), (0 2), (0 1 2), (1 2), (0 2 1), (2)(0 1)]
75
+
76
+ See Also
77
+ ========
78
+
79
+ cyclic
80
+ """
81
+ if n == 1:
82
+ yield Permutation([0, 1])
83
+ yield Permutation([1, 0])
84
+ elif n == 2:
85
+ yield Permutation([0, 1, 2, 3])
86
+ yield Permutation([1, 0, 3, 2])
87
+ yield Permutation([2, 3, 0, 1])
88
+ yield Permutation([3, 2, 1, 0])
89
+ else:
90
+ gen = list(range(n))
91
+ for i in range(n):
92
+ yield Permutation(gen)
93
+ yield Permutation(gen[::-1])
94
+ gen = rotate_left(gen, 1)
95
+
96
+
97
+ def rubik_cube_generators():
98
+ """Return the permutations of the 3x3 Rubik's cube, see
99
+ https://www.gap-system.org/Doc/Examples/rubik.html
100
+ """
101
+ a = [
102
+ [(1, 3, 8, 6), (2, 5, 7, 4), (9, 33, 25, 17), (10, 34, 26, 18),
103
+ (11, 35, 27, 19)],
104
+ [(9, 11, 16, 14), (10, 13, 15, 12), (1, 17, 41, 40), (4, 20, 44, 37),
105
+ (6, 22, 46, 35)],
106
+ [(17, 19, 24, 22), (18, 21, 23, 20), (6, 25, 43, 16), (7, 28, 42, 13),
107
+ (8, 30, 41, 11)],
108
+ [(25, 27, 32, 30), (26, 29, 31, 28), (3, 38, 43, 19), (5, 36, 45, 21),
109
+ (8, 33, 48, 24)],
110
+ [(33, 35, 40, 38), (34, 37, 39, 36), (3, 9, 46, 32), (2, 12, 47, 29),
111
+ (1, 14, 48, 27)],
112
+ [(41, 43, 48, 46), (42, 45, 47, 44), (14, 22, 30, 38),
113
+ (15, 23, 31, 39), (16, 24, 32, 40)]
114
+ ]
115
+ return [Permutation([[i - 1 for i in xi] for xi in x], size=48) for x in a]
116
+
117
+
118
+ def rubik(n):
119
+ """Return permutations for an nxn Rubik's cube.
120
+
121
+ Permutations returned are for rotation of each of the slice
122
+ from the face up to the last face for each of the 3 sides (in this order):
123
+ front, right and bottom. Hence, the first n - 1 permutations are for the
124
+ slices from the front.
125
+ """
126
+
127
+ if n < 2:
128
+ raise ValueError('dimension of cube must be > 1')
129
+
130
+ # 1-based reference to rows and columns in Matrix
131
+ def getr(f, i):
132
+ return faces[f].col(n - i)
133
+
134
+ def getl(f, i):
135
+ return faces[f].col(i - 1)
136
+
137
+ def getu(f, i):
138
+ return faces[f].row(i - 1)
139
+
140
+ def getd(f, i):
141
+ return faces[f].row(n - i)
142
+
143
+ def setr(f, i, s):
144
+ faces[f][:, n - i] = Matrix(n, 1, s)
145
+
146
+ def setl(f, i, s):
147
+ faces[f][:, i - 1] = Matrix(n, 1, s)
148
+
149
+ def setu(f, i, s):
150
+ faces[f][i - 1, :] = Matrix(1, n, s)
151
+
152
+ def setd(f, i, s):
153
+ faces[f][n - i, :] = Matrix(1, n, s)
154
+
155
+ # motion of a single face
156
+ def cw(F, r=1):
157
+ for _ in range(r):
158
+ face = faces[F]
159
+ rv = []
160
+ for c in range(n):
161
+ for r in range(n - 1, -1, -1):
162
+ rv.append(face[r, c])
163
+ faces[F] = Matrix(n, n, rv)
164
+
165
+ def ccw(F):
166
+ cw(F, 3)
167
+
168
+ # motion of plane i from the F side;
169
+ # fcw(0) moves the F face, fcw(1) moves the plane
170
+ # just behind the front face, etc...
171
+ def fcw(i, r=1):
172
+ for _ in range(r):
173
+ if i == 0:
174
+ cw(F)
175
+ i += 1
176
+ temp = getr(L, i)
177
+ setr(L, i, list(getu(D, i)))
178
+ setu(D, i, list(reversed(getl(R, i))))
179
+ setl(R, i, list(getd(U, i)))
180
+ setd(U, i, list(reversed(temp)))
181
+ i -= 1
182
+
183
+ def fccw(i):
184
+ fcw(i, 3)
185
+
186
+ # motion of the entire cube from the F side
187
+ def FCW(r=1):
188
+ for _ in range(r):
189
+ cw(F)
190
+ ccw(B)
191
+ cw(U)
192
+ t = faces[U]
193
+ cw(L)
194
+ faces[U] = faces[L]
195
+ cw(D)
196
+ faces[L] = faces[D]
197
+ cw(R)
198
+ faces[D] = faces[R]
199
+ faces[R] = t
200
+
201
+ def FCCW():
202
+ FCW(3)
203
+
204
+ # motion of the entire cube from the U side
205
+ def UCW(r=1):
206
+ for _ in range(r):
207
+ cw(U)
208
+ ccw(D)
209
+ t = faces[F]
210
+ faces[F] = faces[R]
211
+ faces[R] = faces[B]
212
+ faces[B] = faces[L]
213
+ faces[L] = t
214
+
215
+ def UCCW():
216
+ UCW(3)
217
+
218
+ # defining the permutations for the cube
219
+
220
+ U, F, R, B, L, D = names = symbols('U, F, R, B, L, D')
221
+
222
+ # the faces are represented by nxn matrices
223
+ faces = {}
224
+ count = 0
225
+ for fi in range(6):
226
+ f = []
227
+ for a in range(n**2):
228
+ f.append(count)
229
+ count += 1
230
+ faces[names[fi]] = Matrix(n, n, f)
231
+
232
+ # this will either return the value of the current permutation
233
+ # (show != 1) or else append the permutation to the group, g
234
+ def perm(show=0):
235
+ # add perm to the list of perms
236
+ p = []
237
+ for f in names:
238
+ p.extend(faces[f])
239
+ if show:
240
+ return p
241
+ g.append(Permutation(p))
242
+
243
+ g = [] # container for the group's permutations
244
+ I = list(range(6*n**2)) # the identity permutation used for checking
245
+
246
+ # define permutations corresponding to cw rotations of the planes
247
+ # up TO the last plane from that direction; by not including the
248
+ # last plane, the orientation of the cube is maintained.
249
+
250
+ # F slices
251
+ for i in range(n - 1):
252
+ fcw(i)
253
+ perm()
254
+ fccw(i) # restore
255
+ assert perm(1) == I
256
+
257
+ # R slices
258
+ # bring R to front
259
+ UCW()
260
+ for i in range(n - 1):
261
+ fcw(i)
262
+ # put it back in place
263
+ UCCW()
264
+ # record
265
+ perm()
266
+ # restore
267
+ # bring face to front
268
+ UCW()
269
+ fccw(i)
270
+ # restore
271
+ UCCW()
272
+ assert perm(1) == I
273
+
274
+ # D slices
275
+ # bring up bottom
276
+ FCW()
277
+ UCCW()
278
+ FCCW()
279
+ for i in range(n - 1):
280
+ # turn strip
281
+ fcw(i)
282
+ # put bottom back on the bottom
283
+ FCW()
284
+ UCW()
285
+ FCCW()
286
+ # record
287
+ perm()
288
+ # restore
289
+ # bring up bottom
290
+ FCW()
291
+ UCCW()
292
+ FCCW()
293
+ # turn strip
294
+ fccw(i)
295
+ # put bottom back on the bottom
296
+ FCW()
297
+ UCW()
298
+ FCCW()
299
+ assert perm(1) == I
300
+
301
+ return g
.venv/lib/python3.13/site-packages/sympy/combinatorics/graycode.py ADDED
@@ -0,0 +1,430 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sympy.core import Basic, Integer
2
+
3
+ import random
4
+
5
+
6
+ class GrayCode(Basic):
7
+ """
8
+ A Gray code is essentially a Hamiltonian walk on
9
+ a n-dimensional cube with edge length of one.
10
+ The vertices of the cube are represented by vectors
11
+ whose values are binary. The Hamilton walk visits
12
+ each vertex exactly once. The Gray code for a 3d
13
+ cube is ['000','100','110','010','011','111','101',
14
+ '001'].
15
+
16
+ A Gray code solves the problem of sequentially
17
+ generating all possible subsets of n objects in such
18
+ a way that each subset is obtained from the previous
19
+ one by either deleting or adding a single object.
20
+ In the above example, 1 indicates that the object is
21
+ present, and 0 indicates that its absent.
22
+
23
+ Gray codes have applications in statistics as well when
24
+ we want to compute various statistics related to subsets
25
+ in an efficient manner.
26
+
27
+ Examples
28
+ ========
29
+
30
+ >>> from sympy.combinatorics import GrayCode
31
+ >>> a = GrayCode(3)
32
+ >>> list(a.generate_gray())
33
+ ['000', '001', '011', '010', '110', '111', '101', '100']
34
+ >>> a = GrayCode(4)
35
+ >>> list(a.generate_gray())
36
+ ['0000', '0001', '0011', '0010', '0110', '0111', '0101', '0100', \
37
+ '1100', '1101', '1111', '1110', '1010', '1011', '1001', '1000']
38
+
39
+ References
40
+ ==========
41
+
42
+ .. [1] Nijenhuis,A. and Wilf,H.S.(1978).
43
+ Combinatorial Algorithms. Academic Press.
44
+ .. [2] Knuth, D. (2011). The Art of Computer Programming, Vol 4
45
+ Addison Wesley
46
+
47
+
48
+ """
49
+
50
+ _skip = False
51
+ _current = 0
52
+ _rank = None
53
+
54
+ def __new__(cls, n, *args, **kw_args):
55
+ """
56
+ Default constructor.
57
+
58
+ It takes a single argument ``n`` which gives the dimension of the Gray
59
+ code. The starting Gray code string (``start``) or the starting ``rank``
60
+ may also be given; the default is to start at rank = 0 ('0...0').
61
+
62
+ Examples
63
+ ========
64
+
65
+ >>> from sympy.combinatorics import GrayCode
66
+ >>> a = GrayCode(3)
67
+ >>> a
68
+ GrayCode(3)
69
+ >>> a.n
70
+ 3
71
+
72
+ >>> a = GrayCode(3, start='100')
73
+ >>> a.current
74
+ '100'
75
+
76
+ >>> a = GrayCode(4, rank=4)
77
+ >>> a.current
78
+ '0110'
79
+ >>> a.rank
80
+ 4
81
+
82
+ """
83
+ if n < 1 or int(n) != n:
84
+ raise ValueError(
85
+ 'Gray code dimension must be a positive integer, not %i' % n)
86
+ n = Integer(n)
87
+ args = (n,) + args
88
+ obj = Basic.__new__(cls, *args)
89
+ if 'start' in kw_args:
90
+ obj._current = kw_args["start"]
91
+ if len(obj._current) > n:
92
+ raise ValueError('Gray code start has length %i but '
93
+ 'should not be greater than %i' % (len(obj._current), n))
94
+ elif 'rank' in kw_args:
95
+ if int(kw_args["rank"]) != kw_args["rank"]:
96
+ raise ValueError('Gray code rank must be a positive integer, '
97
+ 'not %i' % kw_args["rank"])
98
+ obj._rank = int(kw_args["rank"]) % obj.selections
99
+ obj._current = obj.unrank(n, obj._rank)
100
+ return obj
101
+
102
+ def next(self, delta=1):
103
+ """
104
+ Returns the Gray code a distance ``delta`` (default = 1) from the
105
+ current value in canonical order.
106
+
107
+
108
+ Examples
109
+ ========
110
+
111
+ >>> from sympy.combinatorics import GrayCode
112
+ >>> a = GrayCode(3, start='110')
113
+ >>> a.next().current
114
+ '111'
115
+ >>> a.next(-1).current
116
+ '010'
117
+ """
118
+ return GrayCode(self.n, rank=(self.rank + delta) % self.selections)
119
+
120
+ @property
121
+ def selections(self):
122
+ """
123
+ Returns the number of bit vectors in the Gray code.
124
+
125
+ Examples
126
+ ========
127
+
128
+ >>> from sympy.combinatorics import GrayCode
129
+ >>> a = GrayCode(3)
130
+ >>> a.selections
131
+ 8
132
+ """
133
+ return 2**self.n
134
+
135
+ @property
136
+ def n(self):
137
+ """
138
+ Returns the dimension of the Gray code.
139
+
140
+ Examples
141
+ ========
142
+
143
+ >>> from sympy.combinatorics import GrayCode
144
+ >>> a = GrayCode(5)
145
+ >>> a.n
146
+ 5
147
+ """
148
+ return self.args[0]
149
+
150
+ def generate_gray(self, **hints):
151
+ """
152
+ Generates the sequence of bit vectors of a Gray Code.
153
+
154
+ Examples
155
+ ========
156
+
157
+ >>> from sympy.combinatorics import GrayCode
158
+ >>> a = GrayCode(3)
159
+ >>> list(a.generate_gray())
160
+ ['000', '001', '011', '010', '110', '111', '101', '100']
161
+ >>> list(a.generate_gray(start='011'))
162
+ ['011', '010', '110', '111', '101', '100']
163
+ >>> list(a.generate_gray(rank=4))
164
+ ['110', '111', '101', '100']
165
+
166
+ See Also
167
+ ========
168
+
169
+ skip
170
+
171
+ References
172
+ ==========
173
+
174
+ .. [1] Knuth, D. (2011). The Art of Computer Programming,
175
+ Vol 4, Addison Wesley
176
+
177
+ """
178
+ bits = self.n
179
+ start = None
180
+ if "start" in hints:
181
+ start = hints["start"]
182
+ elif "rank" in hints:
183
+ start = GrayCode.unrank(self.n, hints["rank"])
184
+ if start is not None:
185
+ self._current = start
186
+ current = self.current
187
+ graycode_bin = gray_to_bin(current)
188
+ if len(graycode_bin) > self.n:
189
+ raise ValueError('Gray code start has length %i but should '
190
+ 'not be greater than %i' % (len(graycode_bin), bits))
191
+ self._current = int(current, 2)
192
+ graycode_int = int(''.join(graycode_bin), 2)
193
+ for i in range(graycode_int, 1 << bits):
194
+ if self._skip:
195
+ self._skip = False
196
+ else:
197
+ yield self.current
198
+ bbtc = (i ^ (i + 1))
199
+ gbtc = (bbtc ^ (bbtc >> 1))
200
+ self._current = (self._current ^ gbtc)
201
+ self._current = 0
202
+
203
+ def skip(self):
204
+ """
205
+ Skips the bit generation.
206
+
207
+ Examples
208
+ ========
209
+
210
+ >>> from sympy.combinatorics import GrayCode
211
+ >>> a = GrayCode(3)
212
+ >>> for i in a.generate_gray():
213
+ ... if i == '010':
214
+ ... a.skip()
215
+ ... print(i)
216
+ ...
217
+ 000
218
+ 001
219
+ 011
220
+ 010
221
+ 111
222
+ 101
223
+ 100
224
+
225
+ See Also
226
+ ========
227
+
228
+ generate_gray
229
+ """
230
+ self._skip = True
231
+
232
+ @property
233
+ def rank(self):
234
+ """
235
+ Ranks the Gray code.
236
+
237
+ A ranking algorithm determines the position (or rank)
238
+ of a combinatorial object among all the objects w.r.t.
239
+ a given order. For example, the 4 bit binary reflected
240
+ Gray code (BRGC) '0101' has a rank of 6 as it appears in
241
+ the 6th position in the canonical ordering of the family
242
+ of 4 bit Gray codes.
243
+
244
+ Examples
245
+ ========
246
+
247
+ >>> from sympy.combinatorics import GrayCode
248
+ >>> a = GrayCode(3)
249
+ >>> list(a.generate_gray())
250
+ ['000', '001', '011', '010', '110', '111', '101', '100']
251
+ >>> GrayCode(3, start='100').rank
252
+ 7
253
+ >>> GrayCode(3, rank=7).current
254
+ '100'
255
+
256
+ See Also
257
+ ========
258
+
259
+ unrank
260
+
261
+ References
262
+ ==========
263
+
264
+ .. [1] https://web.archive.org/web/20200224064753/http://statweb.stanford.edu/~susan/courses/s208/node12.html
265
+
266
+ """
267
+ if self._rank is None:
268
+ self._rank = int(gray_to_bin(self.current), 2)
269
+ return self._rank
270
+
271
+ @property
272
+ def current(self):
273
+ """
274
+ Returns the currently referenced Gray code as a bit string.
275
+
276
+ Examples
277
+ ========
278
+
279
+ >>> from sympy.combinatorics import GrayCode
280
+ >>> GrayCode(3, start='100').current
281
+ '100'
282
+ """
283
+ rv = self._current or '0'
284
+ if not isinstance(rv, str):
285
+ rv = bin(rv)[2:]
286
+ return rv.rjust(self.n, '0')
287
+
288
+ @classmethod
289
+ def unrank(self, n, rank):
290
+ """
291
+ Unranks an n-bit sized Gray code of rank k. This method exists
292
+ so that a derivative GrayCode class can define its own code of
293
+ a given rank.
294
+
295
+ The string here is generated in reverse order to allow for tail-call
296
+ optimization.
297
+
298
+ Examples
299
+ ========
300
+
301
+ >>> from sympy.combinatorics import GrayCode
302
+ >>> GrayCode(5, rank=3).current
303
+ '00010'
304
+ >>> GrayCode.unrank(5, 3)
305
+ '00010'
306
+
307
+ See Also
308
+ ========
309
+
310
+ rank
311
+ """
312
+ def _unrank(k, n):
313
+ if n == 1:
314
+ return str(k % 2)
315
+ m = 2**(n - 1)
316
+ if k < m:
317
+ return '0' + _unrank(k, n - 1)
318
+ return '1' + _unrank(m - (k % m) - 1, n - 1)
319
+ return _unrank(rank, n)
320
+
321
+
322
+ def random_bitstring(n):
323
+ """
324
+ Generates a random bitlist of length n.
325
+
326
+ Examples
327
+ ========
328
+
329
+ >>> from sympy.combinatorics.graycode import random_bitstring
330
+ >>> random_bitstring(3) # doctest: +SKIP
331
+ 100
332
+ """
333
+ return ''.join([random.choice('01') for i in range(n)])
334
+
335
+
336
+ def gray_to_bin(bin_list):
337
+ """
338
+ Convert from Gray coding to binary coding.
339
+
340
+ We assume big endian encoding.
341
+
342
+ Examples
343
+ ========
344
+
345
+ >>> from sympy.combinatorics.graycode import gray_to_bin
346
+ >>> gray_to_bin('100')
347
+ '111'
348
+
349
+ See Also
350
+ ========
351
+
352
+ bin_to_gray
353
+ """
354
+ b = [bin_list[0]]
355
+ for i in range(1, len(bin_list)):
356
+ b += str(int(b[i - 1] != bin_list[i]))
357
+ return ''.join(b)
358
+
359
+
360
+ def bin_to_gray(bin_list):
361
+ """
362
+ Convert from binary coding to gray coding.
363
+
364
+ We assume big endian encoding.
365
+
366
+ Examples
367
+ ========
368
+
369
+ >>> from sympy.combinatorics.graycode import bin_to_gray
370
+ >>> bin_to_gray('111')
371
+ '100'
372
+
373
+ See Also
374
+ ========
375
+
376
+ gray_to_bin
377
+ """
378
+ b = [bin_list[0]]
379
+ for i in range(1, len(bin_list)):
380
+ b += str(int(bin_list[i]) ^ int(bin_list[i - 1]))
381
+ return ''.join(b)
382
+
383
+
384
+ def get_subset_from_bitstring(super_set, bitstring):
385
+ """
386
+ Gets the subset defined by the bitstring.
387
+
388
+ Examples
389
+ ========
390
+
391
+ >>> from sympy.combinatorics.graycode import get_subset_from_bitstring
392
+ >>> get_subset_from_bitstring(['a', 'b', 'c', 'd'], '0011')
393
+ ['c', 'd']
394
+ >>> get_subset_from_bitstring(['c', 'a', 'c', 'c'], '1100')
395
+ ['c', 'a']
396
+
397
+ See Also
398
+ ========
399
+
400
+ graycode_subsets
401
+ """
402
+ if len(super_set) != len(bitstring):
403
+ raise ValueError("The sizes of the lists are not equal")
404
+ return [super_set[i] for i, j in enumerate(bitstring)
405
+ if bitstring[i] == '1']
406
+
407
+
408
+ def graycode_subsets(gray_code_set):
409
+ """
410
+ Generates the subsets as enumerated by a Gray code.
411
+
412
+ Examples
413
+ ========
414
+
415
+ >>> from sympy.combinatorics.graycode import graycode_subsets
416
+ >>> list(graycode_subsets(['a', 'b', 'c']))
417
+ [[], ['c'], ['b', 'c'], ['b'], ['a', 'b'], ['a', 'b', 'c'], \
418
+ ['a', 'c'], ['a']]
419
+ >>> list(graycode_subsets(['a', 'b', 'c', 'c']))
420
+ [[], ['c'], ['c', 'c'], ['c'], ['b', 'c'], ['b', 'c', 'c'], \
421
+ ['b', 'c'], ['b'], ['a', 'b'], ['a', 'b', 'c'], ['a', 'b', 'c', 'c'], \
422
+ ['a', 'b', 'c'], ['a', 'c'], ['a', 'c', 'c'], ['a', 'c'], ['a']]
423
+
424
+ See Also
425
+ ========
426
+
427
+ get_subset_from_bitstring
428
+ """
429
+ for bitstring in list(GrayCode(len(gray_code_set)).generate_gray()):
430
+ yield get_subset_from_bitstring(gray_code_set, bitstring)
.venv/lib/python3.13/site-packages/sympy/combinatorics/group_constructs.py ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sympy.combinatorics.perm_groups import PermutationGroup
2
+ from sympy.combinatorics.permutations import Permutation
3
+ from sympy.utilities.iterables import uniq
4
+
5
+ _af_new = Permutation._af_new
6
+
7
+
8
+ def DirectProduct(*groups):
9
+ """
10
+ Returns the direct product of several groups as a permutation group.
11
+
12
+ Explanation
13
+ ===========
14
+
15
+ This is implemented much like the __mul__ procedure for taking the direct
16
+ product of two permutation groups, but the idea of shifting the
17
+ generators is realized in the case of an arbitrary number of groups.
18
+ A call to DirectProduct(G1, G2, ..., Gn) is generally expected to be faster
19
+ than a call to G1*G2*...*Gn (and thus the need for this algorithm).
20
+
21
+ Examples
22
+ ========
23
+
24
+ >>> from sympy.combinatorics.group_constructs import DirectProduct
25
+ >>> from sympy.combinatorics.named_groups import CyclicGroup
26
+ >>> C = CyclicGroup(4)
27
+ >>> G = DirectProduct(C, C, C)
28
+ >>> G.order()
29
+ 64
30
+
31
+ See Also
32
+ ========
33
+
34
+ sympy.combinatorics.perm_groups.PermutationGroup.__mul__
35
+
36
+ """
37
+ degrees = []
38
+ gens_count = []
39
+ total_degree = 0
40
+ total_gens = 0
41
+ for group in groups:
42
+ current_deg = group.degree
43
+ current_num_gens = len(group.generators)
44
+ degrees.append(current_deg)
45
+ total_degree += current_deg
46
+ gens_count.append(current_num_gens)
47
+ total_gens += current_num_gens
48
+ array_gens = []
49
+ for i in range(total_gens):
50
+ array_gens.append(list(range(total_degree)))
51
+ current_gen = 0
52
+ current_deg = 0
53
+ for i in range(len(gens_count)):
54
+ for j in range(current_gen, current_gen + gens_count[i]):
55
+ gen = ((groups[i].generators)[j - current_gen]).array_form
56
+ array_gens[j][current_deg:current_deg + degrees[i]] = \
57
+ [x + current_deg for x in gen]
58
+ current_gen += gens_count[i]
59
+ current_deg += degrees[i]
60
+ perm_gens = list(uniq([_af_new(list(a)) for a in array_gens]))
61
+ return PermutationGroup(perm_gens, dups=False)
.venv/lib/python3.13/site-packages/sympy/combinatorics/group_numbers.py ADDED
@@ -0,0 +1,294 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from itertools import chain, combinations
2
+
3
+ from sympy.external.gmpy import gcd
4
+ from sympy.ntheory.factor_ import factorint
5
+ from sympy.utilities.misc import as_int
6
+
7
+
8
+ def _is_nilpotent_number(factors: dict) -> bool:
9
+ """ Check whether `n` is a nilpotent number.
10
+ Note that ``factors`` is a prime factorization of `n`.
11
+
12
+ This is a low-level helper for ``is_nilpotent_number``, for internal use.
13
+ """
14
+ for p in factors.keys():
15
+ for q, e in factors.items():
16
+ # We want to calculate
17
+ # any(pow(q, k, p) == 1 for k in range(1, e + 1))
18
+ m = 1
19
+ for _ in range(e):
20
+ m = m*q % p
21
+ if m == 1:
22
+ return False
23
+ return True
24
+
25
+
26
+ def is_nilpotent_number(n) -> bool:
27
+ """
28
+ Check whether `n` is a nilpotent number. A number `n` is said to be
29
+ nilpotent if and only if every finite group of order `n` is nilpotent.
30
+ For more information see [1]_.
31
+
32
+ Examples
33
+ ========
34
+
35
+ >>> from sympy.combinatorics.group_numbers import is_nilpotent_number
36
+ >>> from sympy import randprime
37
+ >>> is_nilpotent_number(21)
38
+ False
39
+ >>> is_nilpotent_number(randprime(1, 30)**12)
40
+ True
41
+
42
+ References
43
+ ==========
44
+
45
+ .. [1] Pakianathan, J., Shankar, K., Nilpotent Numbers,
46
+ The American Mathematical Monthly, 107(7), 631-634.
47
+ .. [2] https://oeis.org/A056867
48
+
49
+ """
50
+ n = as_int(n)
51
+ if n <= 0:
52
+ raise ValueError("n must be a positive integer, not %i" % n)
53
+ return _is_nilpotent_number(factorint(n))
54
+
55
+
56
+ def is_abelian_number(n) -> bool:
57
+ """
58
+ Check whether `n` is an abelian number. A number `n` is said to be abelian
59
+ if and only if every finite group of order `n` is abelian. For more
60
+ information see [1]_.
61
+
62
+ Examples
63
+ ========
64
+
65
+ >>> from sympy.combinatorics.group_numbers import is_abelian_number
66
+ >>> from sympy import randprime
67
+ >>> is_abelian_number(4)
68
+ True
69
+ >>> is_abelian_number(randprime(1, 2000)**2)
70
+ True
71
+ >>> is_abelian_number(60)
72
+ False
73
+
74
+ References
75
+ ==========
76
+
77
+ .. [1] Pakianathan, J., Shankar, K., Nilpotent Numbers,
78
+ The American Mathematical Monthly, 107(7), 631-634.
79
+ .. [2] https://oeis.org/A051532
80
+
81
+ """
82
+ n = as_int(n)
83
+ if n <= 0:
84
+ raise ValueError("n must be a positive integer, not %i" % n)
85
+ factors = factorint(n)
86
+ return all(e < 3 for e in factors.values()) and _is_nilpotent_number(factors)
87
+
88
+
89
+ def is_cyclic_number(n) -> bool:
90
+ """
91
+ Check whether `n` is a cyclic number. A number `n` is said to be cyclic
92
+ if and only if every finite group of order `n` is cyclic. For more
93
+ information see [1]_.
94
+
95
+ Examples
96
+ ========
97
+
98
+ >>> from sympy.combinatorics.group_numbers import is_cyclic_number
99
+ >>> from sympy import randprime
100
+ >>> is_cyclic_number(15)
101
+ True
102
+ >>> is_cyclic_number(randprime(1, 2000)**2)
103
+ False
104
+ >>> is_cyclic_number(4)
105
+ False
106
+
107
+ References
108
+ ==========
109
+
110
+ .. [1] Pakianathan, J., Shankar, K., Nilpotent Numbers,
111
+ The American Mathematical Monthly, 107(7), 631-634.
112
+ .. [2] https://oeis.org/A003277
113
+
114
+ """
115
+ n = as_int(n)
116
+ if n <= 0:
117
+ raise ValueError("n must be a positive integer, not %i" % n)
118
+ factors = factorint(n)
119
+ return all(e == 1 for e in factors.values()) and _is_nilpotent_number(factors)
120
+
121
+
122
+ def _holder_formula(prime_factors):
123
+ r""" Number of groups of order `n`.
124
+ where `n` is squarefree and its prime factors are ``prime_factors``.
125
+ i.e., ``n == math.prod(prime_factors)``
126
+
127
+ Explanation
128
+ ===========
129
+
130
+ When `n` is squarefree, the number of groups of order `n` is expressed by
131
+
132
+ .. math ::
133
+ \sum_{d \mid n} \prod_p \frac{p^{c(p, d)} - 1}{p - 1}
134
+
135
+ where `n=de`, `p` is the prime factor of `e`,
136
+ and `c(p, d)` is the number of prime factors `q` of `d` such that `q \equiv 1 \pmod{p}` [2]_.
137
+
138
+ The formula is elegant, but can be improved when implemented as an algorithm.
139
+ Since `n` is assumed to be squarefree, the divisor `d` of `n` can be identified with the power set of prime factors.
140
+ We let `N` be the set of prime factors of `n`.
141
+ `F = \{p \in N : \forall q \in N, q \not\equiv 1 \pmod{p} \}, M = N \setminus F`, we have the following.
142
+
143
+ .. math ::
144
+ \sum_{d \in 2^{M}} \prod_{p \in M \setminus d} \frac{p^{c(p, F \cup d)} - 1}{p - 1}
145
+
146
+ Practically, many prime factors are expected to be members of `F`, thus reducing computation time.
147
+
148
+ Parameters
149
+ ==========
150
+
151
+ prime_factors : set
152
+ The set of prime factors of ``n``. where `n` is squarefree.
153
+
154
+ Returns
155
+ =======
156
+
157
+ int : Number of groups of order ``n``
158
+
159
+ Examples
160
+ ========
161
+
162
+ >>> from sympy.combinatorics.group_numbers import _holder_formula
163
+ >>> _holder_formula({2}) # n = 2
164
+ 1
165
+ >>> _holder_formula({2, 3}) # n = 2*3 = 6
166
+ 2
167
+
168
+ See Also
169
+ ========
170
+
171
+ groups_count
172
+
173
+ References
174
+ ==========
175
+
176
+ .. [1] Otto Holder, Die Gruppen der Ordnungen p^3, pq^2, pqr, p^4,
177
+ Math. Ann. 43 pp. 301-412 (1893).
178
+ http://dx.doi.org/10.1007/BF01443651
179
+ .. [2] John H. Conway, Heiko Dietrich and E.A. O'Brien,
180
+ Counting groups: gnus, moas and other exotica
181
+ The Mathematical Intelligencer 30, 6-15 (2008)
182
+ https://doi.org/10.1007/BF02985731
183
+
184
+ """
185
+ F = {p for p in prime_factors if all(q % p != 1 for q in prime_factors)}
186
+ M = prime_factors - F
187
+
188
+ s = 0
189
+ powerset = chain.from_iterable(combinations(M, r) for r in range(len(M)+1))
190
+ for ps in powerset:
191
+ ps = set(ps)
192
+ prod = 1
193
+ for p in M - ps:
194
+ c = len([q for q in F | ps if q % p == 1])
195
+ prod *= (p**c - 1) // (p - 1)
196
+ if not prod:
197
+ break
198
+ s += prod
199
+ return s
200
+
201
+
202
+ def groups_count(n):
203
+ r""" Number of groups of order `n`.
204
+ In [1]_, ``gnu(n)`` is given, so we follow this notation here as well.
205
+
206
+ Parameters
207
+ ==========
208
+
209
+ n : Integer
210
+ ``n`` is a positive integer
211
+
212
+ Returns
213
+ =======
214
+
215
+ int : ``gnu(n)``
216
+
217
+ Raises
218
+ ======
219
+
220
+ ValueError
221
+ Number of groups of order ``n`` is unknown or not implemented.
222
+ For example, gnu(`2^{11}`) is not yet known.
223
+ On the other hand, gnu(99) is known to be 2,
224
+ but this has not yet been implemented in this function.
225
+
226
+ Examples
227
+ ========
228
+
229
+ >>> from sympy.combinatorics.group_numbers import groups_count
230
+ >>> groups_count(3) # There is only one cyclic group of order 3
231
+ 1
232
+ >>> # There are two groups of order 10: the cyclic group and the dihedral group
233
+ >>> groups_count(10)
234
+ 2
235
+
236
+ See Also
237
+ ========
238
+
239
+ is_cyclic_number
240
+ `n` is cyclic iff gnu(n) = 1
241
+
242
+ References
243
+ ==========
244
+
245
+ .. [1] John H. Conway, Heiko Dietrich and E.A. O'Brien,
246
+ Counting groups: gnus, moas and other exotica
247
+ The Mathematical Intelligencer 30, 6-15 (2008)
248
+ https://doi.org/10.1007/BF02985731
249
+ .. [2] https://oeis.org/A000001
250
+
251
+ """
252
+ n = as_int(n)
253
+ if n <= 0:
254
+ raise ValueError("n must be a positive integer, not %i" % n)
255
+ factors = factorint(n)
256
+ if len(factors) == 1:
257
+ (p, e) = list(factors.items())[0]
258
+ if p == 2:
259
+ A000679 = [1, 1, 2, 5, 14, 51, 267, 2328, 56092, 10494213, 49487367289]
260
+ if e < len(A000679):
261
+ return A000679[e]
262
+ if p == 3:
263
+ A090091 = [1, 1, 2, 5, 15, 67, 504, 9310, 1396077, 5937876645]
264
+ if e < len(A090091):
265
+ return A090091[e]
266
+ if e <= 2: # gnu(p) = 1, gnu(p**2) = 2
267
+ return e
268
+ if e == 3: # gnu(p**3) = 5
269
+ return 5
270
+ if e == 4: # if p is an odd prime, gnu(p**4) = 15
271
+ return 15
272
+ if e == 5: # if p >= 5, gnu(p**5) is expressed by the following equation
273
+ return 61 + 2*p + 2*gcd(p-1, 3) + gcd(p-1, 4)
274
+ if e == 6: # if p >= 6, gnu(p**6) is expressed by the following equation
275
+ return 3*p**2 + 39*p + 344 +\
276
+ 24*gcd(p-1, 3) + 11*gcd(p-1, 4) + 2*gcd(p-1, 5)
277
+ if e == 7: # if p >= 7, gnu(p**7) is expressed by the following equation
278
+ if p == 5:
279
+ return 34297
280
+ return 3*p**5 + 12*p**4 + 44*p**3 + 170*p**2 + 707*p + 2455 +\
281
+ (4*p**2 + 44*p + 291)*gcd(p-1, 3) + (p**2 + 19*p + 135)*gcd(p-1, 4) + \
282
+ (3*p + 31)*gcd(p-1, 5) + 4*gcd(p-1, 7) + 5*gcd(p-1, 8) + gcd(p-1, 9)
283
+ if any(e > 1 for e in factors.values()): # n is not squarefree
284
+ # some known values for small n that have more than 1 factor and are not square free (https://oeis.org/A000001)
285
+ small = {12: 5, 18: 5, 20: 5, 24: 15, 28: 4, 36: 14, 40: 14, 44: 4, 45: 2, 48: 52,
286
+ 50: 5, 52: 5, 54: 15, 56: 13, 60: 13, 63: 4, 68: 5, 72: 50, 75: 3, 76: 4,
287
+ 80: 52, 84: 15, 88: 12, 90: 10, 92: 4}
288
+ if n in small:
289
+ return small[n]
290
+ raise ValueError("Number of groups of order n is unknown or not implemented")
291
+ if len(factors) == 2: # n is squarefree semiprime
292
+ p, q = sorted(factors.keys())
293
+ return 2 if q % p == 1 else 1
294
+ return _holder_formula(set(factors.keys()))
.venv/lib/python3.13/site-packages/sympy/combinatorics/homomorphisms.py ADDED
@@ -0,0 +1,549 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import itertools
2
+ from sympy.combinatorics.fp_groups import FpGroup, FpSubgroup, simplify_presentation
3
+ from sympy.combinatorics.free_groups import FreeGroup
4
+ from sympy.combinatorics.perm_groups import PermutationGroup
5
+ from sympy.core.intfunc import igcd
6
+ from sympy.functions.combinatorial.numbers import totient
7
+ from sympy.core.singleton import S
8
+
9
+ class GroupHomomorphism:
10
+ '''
11
+ A class representing group homomorphisms. Instantiate using `homomorphism()`.
12
+
13
+ References
14
+ ==========
15
+
16
+ .. [1] Holt, D., Eick, B. and O'Brien, E. (2005). Handbook of computational group theory.
17
+
18
+ '''
19
+
20
+ def __init__(self, domain, codomain, images):
21
+ self.domain = domain
22
+ self.codomain = codomain
23
+ self.images = images
24
+ self._inverses = None
25
+ self._kernel = None
26
+ self._image = None
27
+
28
+ def _invs(self):
29
+ '''
30
+ Return a dictionary with `{gen: inverse}` where `gen` is a rewriting
31
+ generator of `codomain` (e.g. strong generator for permutation groups)
32
+ and `inverse` is an element of its preimage
33
+
34
+ '''
35
+ image = self.image()
36
+ inverses = {}
37
+ for k in list(self.images.keys()):
38
+ v = self.images[k]
39
+ if not (v in inverses
40
+ or v.is_identity):
41
+ inverses[v] = k
42
+ if isinstance(self.codomain, PermutationGroup):
43
+ gens = image.strong_gens
44
+ else:
45
+ gens = image.generators
46
+ for g in gens:
47
+ if g in inverses or g.is_identity:
48
+ continue
49
+ w = self.domain.identity
50
+ if isinstance(self.codomain, PermutationGroup):
51
+ parts = image._strong_gens_slp[g][::-1]
52
+ else:
53
+ parts = g
54
+ for s in parts:
55
+ if s in inverses:
56
+ w = w*inverses[s]
57
+ else:
58
+ w = w*inverses[s**-1]**-1
59
+ inverses[g] = w
60
+
61
+ return inverses
62
+
63
+ def invert(self, g):
64
+ '''
65
+ Return an element of the preimage of ``g`` or of each element
66
+ of ``g`` if ``g`` is a list.
67
+
68
+ Explanation
69
+ ===========
70
+
71
+ If the codomain is an FpGroup, the inverse for equal
72
+ elements might not always be the same unless the FpGroup's
73
+ rewriting system is confluent. However, making a system
74
+ confluent can be time-consuming. If it's important, try
75
+ `self.codomain.make_confluent()` first.
76
+
77
+ '''
78
+ from sympy.combinatorics import Permutation
79
+ from sympy.combinatorics.free_groups import FreeGroupElement
80
+ if isinstance(g, (Permutation, FreeGroupElement)):
81
+ if isinstance(self.codomain, FpGroup):
82
+ g = self.codomain.reduce(g)
83
+ if self._inverses is None:
84
+ self._inverses = self._invs()
85
+ image = self.image()
86
+ w = self.domain.identity
87
+ if isinstance(self.codomain, PermutationGroup):
88
+ gens = image.generator_product(g)[::-1]
89
+ else:
90
+ gens = g
91
+ # the following can't be "for s in gens:"
92
+ # because that would be equivalent to
93
+ # "for s in gens.array_form:" when g is
94
+ # a FreeGroupElement. On the other hand,
95
+ # when you call gens by index, the generator
96
+ # (or inverse) at position i is returned.
97
+ for i in range(len(gens)):
98
+ s = gens[i]
99
+ if s.is_identity:
100
+ continue
101
+ if s in self._inverses:
102
+ w = w*self._inverses[s]
103
+ else:
104
+ w = w*self._inverses[s**-1]**-1
105
+ return w
106
+ elif isinstance(g, list):
107
+ return [self.invert(e) for e in g]
108
+
109
+ def kernel(self):
110
+ '''
111
+ Compute the kernel of `self`.
112
+
113
+ '''
114
+ if self._kernel is None:
115
+ self._kernel = self._compute_kernel()
116
+ return self._kernel
117
+
118
+ def _compute_kernel(self):
119
+ G = self.domain
120
+ G_order = G.order()
121
+ if G_order is S.Infinity:
122
+ raise NotImplementedError(
123
+ "Kernel computation is not implemented for infinite groups")
124
+ gens = []
125
+ if isinstance(G, PermutationGroup):
126
+ K = PermutationGroup(G.identity)
127
+ else:
128
+ K = FpSubgroup(G, gens, normal=True)
129
+ i = self.image().order()
130
+ while K.order()*i != G_order:
131
+ r = G.random()
132
+ k = r*self.invert(self(r))**-1
133
+ if k not in K:
134
+ gens.append(k)
135
+ if isinstance(G, PermutationGroup):
136
+ K = PermutationGroup(gens)
137
+ else:
138
+ K = FpSubgroup(G, gens, normal=True)
139
+ return K
140
+
141
+ def image(self):
142
+ '''
143
+ Compute the image of `self`.
144
+
145
+ '''
146
+ if self._image is None:
147
+ values = list(set(self.images.values()))
148
+ if isinstance(self.codomain, PermutationGroup):
149
+ self._image = self.codomain.subgroup(values)
150
+ else:
151
+ self._image = FpSubgroup(self.codomain, values)
152
+ return self._image
153
+
154
+ def _apply(self, elem):
155
+ '''
156
+ Apply `self` to `elem`.
157
+
158
+ '''
159
+ if elem not in self.domain:
160
+ if isinstance(elem, (list, tuple)):
161
+ return [self._apply(e) for e in elem]
162
+ raise ValueError("The supplied element does not belong to the domain")
163
+ if elem.is_identity:
164
+ return self.codomain.identity
165
+ else:
166
+ images = self.images
167
+ value = self.codomain.identity
168
+ if isinstance(self.domain, PermutationGroup):
169
+ gens = self.domain.generator_product(elem, original=True)
170
+ for g in gens:
171
+ if g in self.images:
172
+ value = images[g]*value
173
+ else:
174
+ value = images[g**-1]**-1*value
175
+ else:
176
+ i = 0
177
+ for _, p in elem.array_form:
178
+ if p < 0:
179
+ g = elem[i]**-1
180
+ else:
181
+ g = elem[i]
182
+ value = value*images[g]**p
183
+ i += abs(p)
184
+ return value
185
+
186
+ def __call__(self, elem):
187
+ return self._apply(elem)
188
+
189
+ def is_injective(self):
190
+ '''
191
+ Check if the homomorphism is injective
192
+
193
+ '''
194
+ return self.kernel().order() == 1
195
+
196
+ def is_surjective(self):
197
+ '''
198
+ Check if the homomorphism is surjective
199
+
200
+ '''
201
+ im = self.image().order()
202
+ oth = self.codomain.order()
203
+ if im is S.Infinity and oth is S.Infinity:
204
+ return None
205
+ else:
206
+ return im == oth
207
+
208
+ def is_isomorphism(self):
209
+ '''
210
+ Check if `self` is an isomorphism.
211
+
212
+ '''
213
+ return self.is_injective() and self.is_surjective()
214
+
215
+ def is_trivial(self):
216
+ '''
217
+ Check is `self` is a trivial homomorphism, i.e. all elements
218
+ are mapped to the identity.
219
+
220
+ '''
221
+ return self.image().order() == 1
222
+
223
+ def compose(self, other):
224
+ '''
225
+ Return the composition of `self` and `other`, i.e.
226
+ the homomorphism phi such that for all g in the domain
227
+ of `other`, phi(g) = self(other(g))
228
+
229
+ '''
230
+ if not other.image().is_subgroup(self.domain):
231
+ raise ValueError("The image of `other` must be a subgroup of "
232
+ "the domain of `self`")
233
+ images = {g: self(other(g)) for g in other.images}
234
+ return GroupHomomorphism(other.domain, self.codomain, images)
235
+
236
+ def restrict_to(self, H):
237
+ '''
238
+ Return the restriction of the homomorphism to the subgroup `H`
239
+ of the domain.
240
+
241
+ '''
242
+ if not isinstance(H, PermutationGroup) or not H.is_subgroup(self.domain):
243
+ raise ValueError("Given H is not a subgroup of the domain")
244
+ domain = H
245
+ images = {g: self(g) for g in H.generators}
246
+ return GroupHomomorphism(domain, self.codomain, images)
247
+
248
+ def invert_subgroup(self, H):
249
+ '''
250
+ Return the subgroup of the domain that is the inverse image
251
+ of the subgroup ``H`` of the homomorphism image
252
+
253
+ '''
254
+ if not H.is_subgroup(self.image()):
255
+ raise ValueError("Given H is not a subgroup of the image")
256
+ gens = []
257
+ P = PermutationGroup(self.image().identity)
258
+ for h in H.generators:
259
+ h_i = self.invert(h)
260
+ if h_i not in P:
261
+ gens.append(h_i)
262
+ P = PermutationGroup(gens)
263
+ for k in self.kernel().generators:
264
+ if k*h_i not in P:
265
+ gens.append(k*h_i)
266
+ P = PermutationGroup(gens)
267
+ return P
268
+
269
+ def homomorphism(domain, codomain, gens, images=(), check=True):
270
+ '''
271
+ Create (if possible) a group homomorphism from the group ``domain``
272
+ to the group ``codomain`` defined by the images of the domain's
273
+ generators ``gens``. ``gens`` and ``images`` can be either lists or tuples
274
+ of equal sizes. If ``gens`` is a proper subset of the group's generators,
275
+ the unspecified generators will be mapped to the identity. If the
276
+ images are not specified, a trivial homomorphism will be created.
277
+
278
+ If the given images of the generators do not define a homomorphism,
279
+ an exception is raised.
280
+
281
+ If ``check`` is ``False``, do not check whether the given images actually
282
+ define a homomorphism.
283
+
284
+ '''
285
+ if not isinstance(domain, (PermutationGroup, FpGroup, FreeGroup)):
286
+ raise TypeError("The domain must be a group")
287
+ if not isinstance(codomain, (PermutationGroup, FpGroup, FreeGroup)):
288
+ raise TypeError("The codomain must be a group")
289
+
290
+ generators = domain.generators
291
+ if not all(g in generators for g in gens):
292
+ raise ValueError("The supplied generators must be a subset of the domain's generators")
293
+ if not all(g in codomain for g in images):
294
+ raise ValueError("The images must be elements of the codomain")
295
+
296
+ if images and len(images) != len(gens):
297
+ raise ValueError("The number of images must be equal to the number of generators")
298
+
299
+ gens = list(gens)
300
+ images = list(images)
301
+
302
+ images.extend([codomain.identity]*(len(generators)-len(images)))
303
+ gens.extend([g for g in generators if g not in gens])
304
+ images = dict(zip(gens,images))
305
+
306
+ if check and not _check_homomorphism(domain, codomain, images):
307
+ raise ValueError("The given images do not define a homomorphism")
308
+ return GroupHomomorphism(domain, codomain, images)
309
+
310
+ def _check_homomorphism(domain, codomain, images):
311
+ """
312
+ Check that a given mapping of generators to images defines a homomorphism.
313
+
314
+ Parameters
315
+ ==========
316
+ domain : PermutationGroup, FpGroup, FreeGroup
317
+ codomain : PermutationGroup, FpGroup, FreeGroup
318
+ images : dict
319
+ The set of keys must be equal to domain.generators.
320
+ The values must be elements of the codomain.
321
+
322
+ """
323
+ pres = domain if hasattr(domain, 'relators') else domain.presentation()
324
+ rels = pres.relators
325
+ gens = pres.generators
326
+ symbols = [g.ext_rep[0] for g in gens]
327
+ symbols_to_domain_generators = dict(zip(symbols, domain.generators))
328
+ identity = codomain.identity
329
+
330
+ def _image(r):
331
+ w = identity
332
+ for symbol, power in r.array_form:
333
+ g = symbols_to_domain_generators[symbol]
334
+ w *= images[g]**power
335
+ return w
336
+
337
+ for r in rels:
338
+ if isinstance(codomain, FpGroup):
339
+ s = codomain.equals(_image(r), identity)
340
+ if s is None:
341
+ # only try to make the rewriting system
342
+ # confluent when it can't determine the
343
+ # truth of equality otherwise
344
+ success = codomain.make_confluent()
345
+ s = codomain.equals(_image(r), identity)
346
+ if s is None and not success:
347
+ raise RuntimeError("Can't determine if the images "
348
+ "define a homomorphism. Try increasing "
349
+ "the maximum number of rewriting rules "
350
+ "(group._rewriting_system.set_max(new_value); "
351
+ "the current value is stored in group._rewriting"
352
+ "_system.maxeqns)")
353
+ else:
354
+ s = _image(r).is_identity
355
+ if not s:
356
+ return False
357
+ return True
358
+
359
+ def orbit_homomorphism(group, omega):
360
+ '''
361
+ Return the homomorphism induced by the action of the permutation
362
+ group ``group`` on the set ``omega`` that is closed under the action.
363
+
364
+ '''
365
+ from sympy.combinatorics import Permutation
366
+ from sympy.combinatorics.named_groups import SymmetricGroup
367
+ codomain = SymmetricGroup(len(omega))
368
+ identity = codomain.identity
369
+ omega = list(omega)
370
+ images = {g: identity*Permutation([omega.index(o^g) for o in omega]) for g in group.generators}
371
+ group._schreier_sims(base=omega)
372
+ H = GroupHomomorphism(group, codomain, images)
373
+ if len(group.basic_stabilizers) > len(omega):
374
+ H._kernel = group.basic_stabilizers[len(omega)]
375
+ else:
376
+ H._kernel = PermutationGroup([group.identity])
377
+ return H
378
+
379
+ def block_homomorphism(group, blocks):
380
+ '''
381
+ Return the homomorphism induced by the action of the permutation
382
+ group ``group`` on the block system ``blocks``. The latter should be
383
+ of the same form as returned by the ``minimal_block`` method for
384
+ permutation groups, namely a list of length ``group.degree`` where
385
+ the i-th entry is a representative of the block i belongs to.
386
+
387
+ '''
388
+ from sympy.combinatorics import Permutation
389
+ from sympy.combinatorics.named_groups import SymmetricGroup
390
+
391
+ n = len(blocks)
392
+
393
+ # number the blocks; m is the total number,
394
+ # b is such that b[i] is the number of the block i belongs to,
395
+ # p is the list of length m such that p[i] is the representative
396
+ # of the i-th block
397
+ m = 0
398
+ p = []
399
+ b = [None]*n
400
+ for i in range(n):
401
+ if blocks[i] == i:
402
+ p.append(i)
403
+ b[i] = m
404
+ m += 1
405
+ for i in range(n):
406
+ b[i] = b[blocks[i]]
407
+
408
+ codomain = SymmetricGroup(m)
409
+ # the list corresponding to the identity permutation in codomain
410
+ identity = range(m)
411
+ images = {g: Permutation([b[p[i]^g] for i in identity]) for g in group.generators}
412
+ H = GroupHomomorphism(group, codomain, images)
413
+ return H
414
+
415
+ def group_isomorphism(G, H, isomorphism=True):
416
+ '''
417
+ Compute an isomorphism between 2 given groups.
418
+
419
+ Parameters
420
+ ==========
421
+
422
+ G : A finite ``FpGroup`` or a ``PermutationGroup``.
423
+ First group.
424
+
425
+ H : A finite ``FpGroup`` or a ``PermutationGroup``
426
+ Second group.
427
+
428
+ isomorphism : bool
429
+ This is used to avoid the computation of homomorphism
430
+ when the user only wants to check if there exists
431
+ an isomorphism between the groups.
432
+
433
+ Returns
434
+ =======
435
+
436
+ If isomorphism = False -- Returns a boolean.
437
+ If isomorphism = True -- Returns a boolean and an isomorphism between `G` and `H`.
438
+
439
+ Examples
440
+ ========
441
+
442
+ >>> from sympy.combinatorics import free_group, Permutation
443
+ >>> from sympy.combinatorics.perm_groups import PermutationGroup
444
+ >>> from sympy.combinatorics.fp_groups import FpGroup
445
+ >>> from sympy.combinatorics.homomorphisms import group_isomorphism
446
+ >>> from sympy.combinatorics.named_groups import DihedralGroup, AlternatingGroup
447
+
448
+ >>> D = DihedralGroup(8)
449
+ >>> p = Permutation(0, 1, 2, 3, 4, 5, 6, 7)
450
+ >>> P = PermutationGroup(p)
451
+ >>> group_isomorphism(D, P)
452
+ (False, None)
453
+
454
+ >>> F, a, b = free_group("a, b")
455
+ >>> G = FpGroup(F, [a**3, b**3, (a*b)**2])
456
+ >>> H = AlternatingGroup(4)
457
+ >>> (check, T) = group_isomorphism(G, H)
458
+ >>> check
459
+ True
460
+ >>> T(b*a*b**-1*a**-1*b**-1)
461
+ (0 2 3)
462
+
463
+ Notes
464
+ =====
465
+
466
+ Uses the approach suggested by Robert Tarjan to compute the isomorphism between two groups.
467
+ First, the generators of ``G`` are mapped to the elements of ``H`` and
468
+ we check if the mapping induces an isomorphism.
469
+
470
+ '''
471
+ if not isinstance(G, (PermutationGroup, FpGroup)):
472
+ raise TypeError("The group must be a PermutationGroup or an FpGroup")
473
+ if not isinstance(H, (PermutationGroup, FpGroup)):
474
+ raise TypeError("The group must be a PermutationGroup or an FpGroup")
475
+
476
+ if isinstance(G, FpGroup) and isinstance(H, FpGroup):
477
+ G = simplify_presentation(G)
478
+ H = simplify_presentation(H)
479
+ # Two infinite FpGroups with the same generators are isomorphic
480
+ # when the relators are same but are ordered differently.
481
+ if G.generators == H.generators and (G.relators).sort() == (H.relators).sort():
482
+ if not isomorphism:
483
+ return True
484
+ return (True, homomorphism(G, H, G.generators, H.generators))
485
+
486
+ # `_H` is the permutation group isomorphic to `H`.
487
+ _H = H
488
+ g_order = G.order()
489
+ h_order = H.order()
490
+
491
+ if g_order is S.Infinity:
492
+ raise NotImplementedError("Isomorphism methods are not implemented for infinite groups.")
493
+
494
+ if isinstance(H, FpGroup):
495
+ if h_order is S.Infinity:
496
+ raise NotImplementedError("Isomorphism methods are not implemented for infinite groups.")
497
+ _H, h_isomorphism = H._to_perm_group()
498
+
499
+ if (g_order != h_order) or (G.is_abelian != H.is_abelian):
500
+ if not isomorphism:
501
+ return False
502
+ return (False, None)
503
+
504
+ if not isomorphism:
505
+ # Two groups of the same cyclic numbered order
506
+ # are isomorphic to each other.
507
+ n = g_order
508
+ if (igcd(n, totient(n))) == 1:
509
+ return True
510
+
511
+ # Match the generators of `G` with subsets of `_H`
512
+ gens = list(G.generators)
513
+ for subset in itertools.permutations(_H, len(gens)):
514
+ images = list(subset)
515
+ images.extend([_H.identity]*(len(G.generators)-len(images)))
516
+ _images = dict(zip(gens,images))
517
+ if _check_homomorphism(G, _H, _images):
518
+ if isinstance(H, FpGroup):
519
+ images = h_isomorphism.invert(images)
520
+ T = homomorphism(G, H, G.generators, images, check=False)
521
+ if T.is_isomorphism():
522
+ # It is a valid isomorphism
523
+ if not isomorphism:
524
+ return True
525
+ return (True, T)
526
+
527
+ if not isomorphism:
528
+ return False
529
+ return (False, None)
530
+
531
+ def is_isomorphic(G, H):
532
+ '''
533
+ Check if the groups are isomorphic to each other
534
+
535
+ Parameters
536
+ ==========
537
+
538
+ G : A finite ``FpGroup`` or a ``PermutationGroup``
539
+ First group.
540
+
541
+ H : A finite ``FpGroup`` or a ``PermutationGroup``
542
+ Second group.
543
+
544
+ Returns
545
+ =======
546
+
547
+ boolean
548
+ '''
549
+ return group_isomorphism(G, H, isomorphism=False)
.venv/lib/python3.13/site-packages/sympy/combinatorics/named_groups.py ADDED
@@ -0,0 +1,332 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sympy.combinatorics.group_constructs import DirectProduct
2
+ from sympy.combinatorics.perm_groups import PermutationGroup
3
+ from sympy.combinatorics.permutations import Permutation
4
+
5
+ _af_new = Permutation._af_new
6
+
7
+
8
+ def AbelianGroup(*cyclic_orders):
9
+ """
10
+ Returns the direct product of cyclic groups with the given orders.
11
+
12
+ Explanation
13
+ ===========
14
+
15
+ According to the structure theorem for finite abelian groups ([1]),
16
+ every finite abelian group can be written as the direct product of
17
+ finitely many cyclic groups.
18
+
19
+ Examples
20
+ ========
21
+
22
+ >>> from sympy.combinatorics.named_groups import AbelianGroup
23
+ >>> AbelianGroup(3, 4)
24
+ PermutationGroup([
25
+ (6)(0 1 2),
26
+ (3 4 5 6)])
27
+ >>> _.is_group
28
+ True
29
+
30
+ See Also
31
+ ========
32
+
33
+ DirectProduct
34
+
35
+ References
36
+ ==========
37
+
38
+ .. [1] https://groupprops.subwiki.org/wiki/Structure_theorem_for_finitely_generated_abelian_groups
39
+
40
+ """
41
+ groups = []
42
+ degree = 0
43
+ order = 1
44
+ for size in cyclic_orders:
45
+ degree += size
46
+ order *= size
47
+ groups.append(CyclicGroup(size))
48
+ G = DirectProduct(*groups)
49
+ G._is_abelian = True
50
+ G._degree = degree
51
+ G._order = order
52
+
53
+ return G
54
+
55
+
56
+ def AlternatingGroup(n):
57
+ """
58
+ Generates the alternating group on ``n`` elements as a permutation group.
59
+
60
+ Explanation
61
+ ===========
62
+
63
+ For ``n > 2``, the generators taken are ``(0 1 2), (0 1 2 ... n-1)`` for
64
+ ``n`` odd
65
+ and ``(0 1 2), (1 2 ... n-1)`` for ``n`` even (See [1], p.31, ex.6.9.).
66
+ After the group is generated, some of its basic properties are set.
67
+ The cases ``n = 1, 2`` are handled separately.
68
+
69
+ Examples
70
+ ========
71
+
72
+ >>> from sympy.combinatorics.named_groups import AlternatingGroup
73
+ >>> G = AlternatingGroup(4)
74
+ >>> G.is_group
75
+ True
76
+ >>> a = list(G.generate_dimino())
77
+ >>> len(a)
78
+ 12
79
+ >>> all(perm.is_even for perm in a)
80
+ True
81
+
82
+ See Also
83
+ ========
84
+
85
+ SymmetricGroup, CyclicGroup, DihedralGroup
86
+
87
+ References
88
+ ==========
89
+
90
+ .. [1] Armstrong, M. "Groups and Symmetry"
91
+
92
+ """
93
+ # small cases are special
94
+ if n in (1, 2):
95
+ return PermutationGroup([Permutation([0])])
96
+
97
+ a = list(range(n))
98
+ a[0], a[1], a[2] = a[1], a[2], a[0]
99
+ gen1 = a
100
+ if n % 2:
101
+ a = list(range(1, n))
102
+ a.append(0)
103
+ gen2 = a
104
+ else:
105
+ a = list(range(2, n))
106
+ a.append(1)
107
+ a.insert(0, 0)
108
+ gen2 = a
109
+ gens = [gen1, gen2]
110
+ if gen1 == gen2:
111
+ gens = gens[:1]
112
+ G = PermutationGroup([_af_new(a) for a in gens], dups=False)
113
+
114
+ set_alternating_group_properties(G, n, n)
115
+ G._is_alt = True
116
+ return G
117
+
118
+
119
+ def set_alternating_group_properties(G, n, degree):
120
+ """Set known properties of an alternating group. """
121
+ if n < 4:
122
+ G._is_abelian = True
123
+ G._is_nilpotent = True
124
+ else:
125
+ G._is_abelian = False
126
+ G._is_nilpotent = False
127
+ if n < 5:
128
+ G._is_solvable = True
129
+ else:
130
+ G._is_solvable = False
131
+ G._degree = degree
132
+ G._is_transitive = True
133
+ G._is_dihedral = False
134
+
135
+
136
+ def CyclicGroup(n):
137
+ """
138
+ Generates the cyclic group of order ``n`` as a permutation group.
139
+
140
+ Explanation
141
+ ===========
142
+
143
+ The generator taken is the ``n``-cycle ``(0 1 2 ... n-1)``
144
+ (in cycle notation). After the group is generated, some of its basic
145
+ properties are set.
146
+
147
+ Examples
148
+ ========
149
+
150
+ >>> from sympy.combinatorics.named_groups import CyclicGroup
151
+ >>> G = CyclicGroup(6)
152
+ >>> G.is_group
153
+ True
154
+ >>> G.order()
155
+ 6
156
+ >>> list(G.generate_schreier_sims(af=True))
157
+ [[0, 1, 2, 3, 4, 5], [1, 2, 3, 4, 5, 0], [2, 3, 4, 5, 0, 1],
158
+ [3, 4, 5, 0, 1, 2], [4, 5, 0, 1, 2, 3], [5, 0, 1, 2, 3, 4]]
159
+
160
+ See Also
161
+ ========
162
+
163
+ SymmetricGroup, DihedralGroup, AlternatingGroup
164
+
165
+ """
166
+ a = list(range(1, n))
167
+ a.append(0)
168
+ gen = _af_new(a)
169
+ G = PermutationGroup([gen])
170
+
171
+ G._is_abelian = True
172
+ G._is_nilpotent = True
173
+ G._is_solvable = True
174
+ G._degree = n
175
+ G._is_transitive = True
176
+ G._order = n
177
+ G._is_dihedral = (n == 2)
178
+ return G
179
+
180
+
181
+ def DihedralGroup(n):
182
+ r"""
183
+ Generates the dihedral group `D_n` as a permutation group.
184
+
185
+ Explanation
186
+ ===========
187
+
188
+ The dihedral group `D_n` is the group of symmetries of the regular
189
+ ``n``-gon. The generators taken are the ``n``-cycle ``a = (0 1 2 ... n-1)``
190
+ (a rotation of the ``n``-gon) and ``b = (0 n-1)(1 n-2)...``
191
+ (a reflection of the ``n``-gon) in cycle rotation. It is easy to see that
192
+ these satisfy ``a**n = b**2 = 1`` and ``bab = ~a`` so they indeed generate
193
+ `D_n` (See [1]). After the group is generated, some of its basic properties
194
+ are set.
195
+
196
+ Examples
197
+ ========
198
+
199
+ >>> from sympy.combinatorics.named_groups import DihedralGroup
200
+ >>> G = DihedralGroup(5)
201
+ >>> G.is_group
202
+ True
203
+ >>> a = list(G.generate_dimino())
204
+ >>> [perm.cyclic_form for perm in a]
205
+ [[], [[0, 1, 2, 3, 4]], [[0, 2, 4, 1, 3]],
206
+ [[0, 3, 1, 4, 2]], [[0, 4, 3, 2, 1]], [[0, 4], [1, 3]],
207
+ [[1, 4], [2, 3]], [[0, 1], [2, 4]], [[0, 2], [3, 4]],
208
+ [[0, 3], [1, 2]]]
209
+
210
+ See Also
211
+ ========
212
+
213
+ SymmetricGroup, CyclicGroup, AlternatingGroup
214
+
215
+ References
216
+ ==========
217
+
218
+ .. [1] https://en.wikipedia.org/wiki/Dihedral_group
219
+
220
+ """
221
+ # small cases are special
222
+ if n == 1:
223
+ return PermutationGroup([Permutation([1, 0])])
224
+ if n == 2:
225
+ return PermutationGroup([Permutation([1, 0, 3, 2]),
226
+ Permutation([2, 3, 0, 1]), Permutation([3, 2, 1, 0])])
227
+
228
+ a = list(range(1, n))
229
+ a.append(0)
230
+ gen1 = _af_new(a)
231
+ a = list(range(n))
232
+ a.reverse()
233
+ gen2 = _af_new(a)
234
+ G = PermutationGroup([gen1, gen2])
235
+ # if n is a power of 2, group is nilpotent
236
+ if n & (n-1) == 0:
237
+ G._is_nilpotent = True
238
+ else:
239
+ G._is_nilpotent = False
240
+ G._is_dihedral = True
241
+ G._is_abelian = False
242
+ G._is_solvable = True
243
+ G._degree = n
244
+ G._is_transitive = True
245
+ G._order = 2*n
246
+ return G
247
+
248
+
249
+ def SymmetricGroup(n):
250
+ """
251
+ Generates the symmetric group on ``n`` elements as a permutation group.
252
+
253
+ Explanation
254
+ ===========
255
+
256
+ The generators taken are the ``n``-cycle
257
+ ``(0 1 2 ... n-1)`` and the transposition ``(0 1)`` (in cycle notation).
258
+ (See [1]). After the group is generated, some of its basic properties
259
+ are set.
260
+
261
+ Examples
262
+ ========
263
+
264
+ >>> from sympy.combinatorics.named_groups import SymmetricGroup
265
+ >>> G = SymmetricGroup(4)
266
+ >>> G.is_group
267
+ True
268
+ >>> G.order()
269
+ 24
270
+ >>> list(G.generate_schreier_sims(af=True))
271
+ [[0, 1, 2, 3], [1, 2, 3, 0], [2, 3, 0, 1], [3, 1, 2, 0], [0, 2, 3, 1],
272
+ [1, 3, 0, 2], [2, 0, 1, 3], [3, 2, 0, 1], [0, 3, 1, 2], [1, 0, 2, 3],
273
+ [2, 1, 3, 0], [3, 0, 1, 2], [0, 1, 3, 2], [1, 2, 0, 3], [2, 3, 1, 0],
274
+ [3, 1, 0, 2], [0, 2, 1, 3], [1, 3, 2, 0], [2, 0, 3, 1], [3, 2, 1, 0],
275
+ [0, 3, 2, 1], [1, 0, 3, 2], [2, 1, 0, 3], [3, 0, 2, 1]]
276
+
277
+ See Also
278
+ ========
279
+
280
+ CyclicGroup, DihedralGroup, AlternatingGroup
281
+
282
+ References
283
+ ==========
284
+
285
+ .. [1] https://en.wikipedia.org/wiki/Symmetric_group#Generators_and_relations
286
+
287
+ """
288
+ if n == 1:
289
+ G = PermutationGroup([Permutation([0])])
290
+ elif n == 2:
291
+ G = PermutationGroup([Permutation([1, 0])])
292
+ else:
293
+ a = list(range(1, n))
294
+ a.append(0)
295
+ gen1 = _af_new(a)
296
+ a = list(range(n))
297
+ a[0], a[1] = a[1], a[0]
298
+ gen2 = _af_new(a)
299
+ G = PermutationGroup([gen1, gen2])
300
+ set_symmetric_group_properties(G, n, n)
301
+ G._is_sym = True
302
+ return G
303
+
304
+
305
+ def set_symmetric_group_properties(G, n, degree):
306
+ """Set known properties of a symmetric group. """
307
+ if n < 3:
308
+ G._is_abelian = True
309
+ G._is_nilpotent = True
310
+ else:
311
+ G._is_abelian = False
312
+ G._is_nilpotent = False
313
+ if n < 5:
314
+ G._is_solvable = True
315
+ else:
316
+ G._is_solvable = False
317
+ G._degree = degree
318
+ G._is_transitive = True
319
+ G._is_dihedral = (n in [2, 3]) # cf Landau's func and Stirling's approx
320
+
321
+
322
+ def RubikGroup(n):
323
+ """Return a group of Rubik's cube generators
324
+
325
+ >>> from sympy.combinatorics.named_groups import RubikGroup
326
+ >>> RubikGroup(2).is_group
327
+ True
328
+ """
329
+ from sympy.combinatorics.generators import rubik
330
+ if n <= 1:
331
+ raise ValueError("Invalid cube. n has to be greater than 1")
332
+ return PermutationGroup(rubik(n))
.venv/lib/python3.13/site-packages/sympy/combinatorics/partitions.py ADDED
@@ -0,0 +1,745 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sympy.core import Basic, Dict, sympify, Tuple
2
+ from sympy.core.numbers import Integer
3
+ from sympy.core.sorting import default_sort_key
4
+ from sympy.core.sympify import _sympify
5
+ from sympy.functions.combinatorial.numbers import bell
6
+ from sympy.matrices import zeros
7
+ from sympy.sets.sets import FiniteSet, Union
8
+ from sympy.utilities.iterables import flatten, group
9
+ from sympy.utilities.misc import as_int
10
+
11
+
12
+ from collections import defaultdict
13
+
14
+
15
+ class Partition(FiniteSet):
16
+ """
17
+ This class represents an abstract partition.
18
+
19
+ A partition is a set of disjoint sets whose union equals a given set.
20
+
21
+ See Also
22
+ ========
23
+
24
+ sympy.utilities.iterables.partitions,
25
+ sympy.utilities.iterables.multiset_partitions
26
+ """
27
+
28
+ _rank = None
29
+ _partition = None
30
+
31
+ def __new__(cls, *partition):
32
+ """
33
+ Generates a new partition object.
34
+
35
+ This method also verifies if the arguments passed are
36
+ valid and raises a ValueError if they are not.
37
+
38
+ Examples
39
+ ========
40
+
41
+ Creating Partition from Python lists:
42
+
43
+ >>> from sympy.combinatorics import Partition
44
+ >>> a = Partition([1, 2], [3])
45
+ >>> a
46
+ Partition({3}, {1, 2})
47
+ >>> a.partition
48
+ [[1, 2], [3]]
49
+ >>> len(a)
50
+ 2
51
+ >>> a.members
52
+ (1, 2, 3)
53
+
54
+ Creating Partition from Python sets:
55
+
56
+ >>> Partition({1, 2, 3}, {4, 5})
57
+ Partition({4, 5}, {1, 2, 3})
58
+
59
+ Creating Partition from SymPy finite sets:
60
+
61
+ >>> from sympy import FiniteSet
62
+ >>> a = FiniteSet(1, 2, 3)
63
+ >>> b = FiniteSet(4, 5)
64
+ >>> Partition(a, b)
65
+ Partition({4, 5}, {1, 2, 3})
66
+ """
67
+ args = []
68
+ dups = False
69
+ for arg in partition:
70
+ if isinstance(arg, list):
71
+ as_set = set(arg)
72
+ if len(as_set) < len(arg):
73
+ dups = True
74
+ break # error below
75
+ arg = as_set
76
+ args.append(_sympify(arg))
77
+
78
+ if not all(isinstance(part, FiniteSet) for part in args):
79
+ raise ValueError(
80
+ "Each argument to Partition should be " \
81
+ "a list, set, or a FiniteSet")
82
+
83
+ # sort so we have a canonical reference for RGS
84
+ U = Union(*args)
85
+ if dups or len(U) < sum(len(arg) for arg in args):
86
+ raise ValueError("Partition contained duplicate elements.")
87
+
88
+ obj = FiniteSet.__new__(cls, *args)
89
+ obj.members = tuple(U)
90
+ obj.size = len(U)
91
+ return obj
92
+
93
+ def sort_key(self, order=None):
94
+ """Return a canonical key that can be used for sorting.
95
+
96
+ Ordering is based on the size and sorted elements of the partition
97
+ and ties are broken with the rank.
98
+
99
+ Examples
100
+ ========
101
+
102
+ >>> from sympy import default_sort_key
103
+ >>> from sympy.combinatorics import Partition
104
+ >>> from sympy.abc import x
105
+ >>> a = Partition([1, 2])
106
+ >>> b = Partition([3, 4])
107
+ >>> c = Partition([1, x])
108
+ >>> d = Partition(list(range(4)))
109
+ >>> l = [d, b, a + 1, a, c]
110
+ >>> l.sort(key=default_sort_key); l
111
+ [Partition({1, 2}), Partition({1}, {2}), Partition({1, x}), Partition({3, 4}), Partition({0, 1, 2, 3})]
112
+ """
113
+ if order is None:
114
+ members = self.members
115
+ else:
116
+ members = tuple(sorted(self.members,
117
+ key=lambda w: default_sort_key(w, order)))
118
+ return tuple(map(default_sort_key, (self.size, members, self.rank)))
119
+
120
+ @property
121
+ def partition(self):
122
+ """Return partition as a sorted list of lists.
123
+
124
+ Examples
125
+ ========
126
+
127
+ >>> from sympy.combinatorics import Partition
128
+ >>> Partition([1], [2, 3]).partition
129
+ [[1], [2, 3]]
130
+ """
131
+ if self._partition is None:
132
+ self._partition = sorted([sorted(p, key=default_sort_key)
133
+ for p in self.args])
134
+ return self._partition
135
+
136
+ def __add__(self, other):
137
+ """
138
+ Return permutation whose rank is ``other`` greater than current rank,
139
+ (mod the maximum rank for the set).
140
+
141
+ Examples
142
+ ========
143
+
144
+ >>> from sympy.combinatorics import Partition
145
+ >>> a = Partition([1, 2], [3])
146
+ >>> a.rank
147
+ 1
148
+ >>> (a + 1).rank
149
+ 2
150
+ >>> (a + 100).rank
151
+ 1
152
+ """
153
+ other = as_int(other)
154
+ offset = self.rank + other
155
+ result = RGS_unrank((offset) %
156
+ RGS_enum(self.size),
157
+ self.size)
158
+ return Partition.from_rgs(result, self.members)
159
+
160
+ def __sub__(self, other):
161
+ """
162
+ Return permutation whose rank is ``other`` less than current rank,
163
+ (mod the maximum rank for the set).
164
+
165
+ Examples
166
+ ========
167
+
168
+ >>> from sympy.combinatorics import Partition
169
+ >>> a = Partition([1, 2], [3])
170
+ >>> a.rank
171
+ 1
172
+ >>> (a - 1).rank
173
+ 0
174
+ >>> (a - 100).rank
175
+ 1
176
+ """
177
+ return self.__add__(-other)
178
+
179
+ def __le__(self, other):
180
+ """
181
+ Checks if a partition is less than or equal to
182
+ the other based on rank.
183
+
184
+ Examples
185
+ ========
186
+
187
+ >>> from sympy.combinatorics import Partition
188
+ >>> a = Partition([1, 2], [3, 4, 5])
189
+ >>> b = Partition([1], [2, 3], [4], [5])
190
+ >>> a.rank, b.rank
191
+ (9, 34)
192
+ >>> a <= a
193
+ True
194
+ >>> a <= b
195
+ True
196
+ """
197
+ return self.sort_key() <= sympify(other).sort_key()
198
+
199
+ def __lt__(self, other):
200
+ """
201
+ Checks if a partition is less than the other.
202
+
203
+ Examples
204
+ ========
205
+
206
+ >>> from sympy.combinatorics import Partition
207
+ >>> a = Partition([1, 2], [3, 4, 5])
208
+ >>> b = Partition([1], [2, 3], [4], [5])
209
+ >>> a.rank, b.rank
210
+ (9, 34)
211
+ >>> a < b
212
+ True
213
+ """
214
+ return self.sort_key() < sympify(other).sort_key()
215
+
216
+ @property
217
+ def rank(self):
218
+ """
219
+ Gets the rank of a partition.
220
+
221
+ Examples
222
+ ========
223
+
224
+ >>> from sympy.combinatorics import Partition
225
+ >>> a = Partition([1, 2], [3], [4, 5])
226
+ >>> a.rank
227
+ 13
228
+ """
229
+ if self._rank is not None:
230
+ return self._rank
231
+ self._rank = RGS_rank(self.RGS)
232
+ return self._rank
233
+
234
+ @property
235
+ def RGS(self):
236
+ """
237
+ Returns the "restricted growth string" of the partition.
238
+
239
+ Explanation
240
+ ===========
241
+
242
+ The RGS is returned as a list of indices, L, where L[i] indicates
243
+ the block in which element i appears. For example, in a partition
244
+ of 3 elements (a, b, c) into 2 blocks ([c], [a, b]) the RGS is
245
+ [1, 1, 0]: "a" is in block 1, "b" is in block 1 and "c" is in block 0.
246
+
247
+ Examples
248
+ ========
249
+
250
+ >>> from sympy.combinatorics import Partition
251
+ >>> a = Partition([1, 2], [3], [4, 5])
252
+ >>> a.members
253
+ (1, 2, 3, 4, 5)
254
+ >>> a.RGS
255
+ (0, 0, 1, 2, 2)
256
+ >>> a + 1
257
+ Partition({3}, {4}, {5}, {1, 2})
258
+ >>> _.RGS
259
+ (0, 0, 1, 2, 3)
260
+ """
261
+ rgs = {}
262
+ partition = self.partition
263
+ for i, part in enumerate(partition):
264
+ for j in part:
265
+ rgs[j] = i
266
+ return tuple([rgs[i] for i in sorted(
267
+ [i for p in partition for i in p], key=default_sort_key)])
268
+
269
+ @classmethod
270
+ def from_rgs(self, rgs, elements):
271
+ """
272
+ Creates a set partition from a restricted growth string.
273
+
274
+ Explanation
275
+ ===========
276
+
277
+ The indices given in rgs are assumed to be the index
278
+ of the element as given in elements *as provided* (the
279
+ elements are not sorted by this routine). Block numbering
280
+ starts from 0. If any block was not referenced in ``rgs``
281
+ an error will be raised.
282
+
283
+ Examples
284
+ ========
285
+
286
+ >>> from sympy.combinatorics import Partition
287
+ >>> Partition.from_rgs([0, 1, 2, 0, 1], list('abcde'))
288
+ Partition({c}, {a, d}, {b, e})
289
+ >>> Partition.from_rgs([0, 1, 2, 0, 1], list('cbead'))
290
+ Partition({e}, {a, c}, {b, d})
291
+ >>> a = Partition([1, 4], [2], [3, 5])
292
+ >>> Partition.from_rgs(a.RGS, a.members)
293
+ Partition({2}, {1, 4}, {3, 5})
294
+ """
295
+ if len(rgs) != len(elements):
296
+ raise ValueError('mismatch in rgs and element lengths')
297
+ max_elem = max(rgs) + 1
298
+ partition = [[] for i in range(max_elem)]
299
+ j = 0
300
+ for i in rgs:
301
+ partition[i].append(elements[j])
302
+ j += 1
303
+ if not all(p for p in partition):
304
+ raise ValueError('some blocks of the partition were empty.')
305
+ return Partition(*partition)
306
+
307
+
308
+ class IntegerPartition(Basic):
309
+ """
310
+ This class represents an integer partition.
311
+
312
+ Explanation
313
+ ===========
314
+
315
+ In number theory and combinatorics, a partition of a positive integer,
316
+ ``n``, also called an integer partition, is a way of writing ``n`` as a
317
+ list of positive integers that sum to n. Two partitions that differ only
318
+ in the order of summands are considered to be the same partition; if order
319
+ matters then the partitions are referred to as compositions. For example,
320
+ 4 has five partitions: [4], [3, 1], [2, 2], [2, 1, 1], and [1, 1, 1, 1];
321
+ the compositions [1, 2, 1] and [1, 1, 2] are the same as partition
322
+ [2, 1, 1].
323
+
324
+ See Also
325
+ ========
326
+
327
+ sympy.utilities.iterables.partitions,
328
+ sympy.utilities.iterables.multiset_partitions
329
+
330
+ References
331
+ ==========
332
+
333
+ .. [1] https://en.wikipedia.org/wiki/Partition_%28number_theory%29
334
+ """
335
+
336
+ _dict = None
337
+ _keys = None
338
+
339
+ def __new__(cls, partition, integer=None):
340
+ """
341
+ Generates a new IntegerPartition object from a list or dictionary.
342
+
343
+ Explanation
344
+ ===========
345
+
346
+ The partition can be given as a list of positive integers or a
347
+ dictionary of (integer, multiplicity) items. If the partition is
348
+ preceded by an integer an error will be raised if the partition
349
+ does not sum to that given integer.
350
+
351
+ Examples
352
+ ========
353
+
354
+ >>> from sympy.combinatorics.partitions import IntegerPartition
355
+ >>> a = IntegerPartition([5, 4, 3, 1, 1])
356
+ >>> a
357
+ IntegerPartition(14, (5, 4, 3, 1, 1))
358
+ >>> print(a)
359
+ [5, 4, 3, 1, 1]
360
+ >>> IntegerPartition({1:3, 2:1})
361
+ IntegerPartition(5, (2, 1, 1, 1))
362
+
363
+ If the value that the partition should sum to is given first, a check
364
+ will be made to see n error will be raised if there is a discrepancy:
365
+
366
+ >>> IntegerPartition(10, [5, 4, 3, 1])
367
+ Traceback (most recent call last):
368
+ ...
369
+ ValueError: The partition is not valid
370
+
371
+ """
372
+ if integer is not None:
373
+ integer, partition = partition, integer
374
+ if isinstance(partition, (dict, Dict)):
375
+ _ = []
376
+ for k, v in sorted(partition.items(), reverse=True):
377
+ if not v:
378
+ continue
379
+ k, v = as_int(k), as_int(v)
380
+ _.extend([k]*v)
381
+ partition = tuple(_)
382
+ else:
383
+ partition = tuple(sorted(map(as_int, partition), reverse=True))
384
+ sum_ok = False
385
+ if integer is None:
386
+ integer = sum(partition)
387
+ sum_ok = True
388
+ else:
389
+ integer = as_int(integer)
390
+
391
+ if not sum_ok and sum(partition) != integer:
392
+ raise ValueError("Partition did not add to %s" % integer)
393
+ if any(i < 1 for i in partition):
394
+ raise ValueError("All integer summands must be greater than one")
395
+
396
+ obj = Basic.__new__(cls, Integer(integer), Tuple(*partition))
397
+ obj.partition = list(partition)
398
+ obj.integer = integer
399
+ return obj
400
+
401
+ def prev_lex(self):
402
+ """Return the previous partition of the integer, n, in lexical order,
403
+ wrapping around to [1, ..., 1] if the partition is [n].
404
+
405
+ Examples
406
+ ========
407
+
408
+ >>> from sympy.combinatorics.partitions import IntegerPartition
409
+ >>> p = IntegerPartition([4])
410
+ >>> print(p.prev_lex())
411
+ [3, 1]
412
+ >>> p.partition > p.prev_lex().partition
413
+ True
414
+ """
415
+ d = defaultdict(int)
416
+ d.update(self.as_dict())
417
+ keys = self._keys
418
+ if keys == [1]:
419
+ return IntegerPartition({self.integer: 1})
420
+ if keys[-1] != 1:
421
+ d[keys[-1]] -= 1
422
+ if keys[-1] == 2:
423
+ d[1] = 2
424
+ else:
425
+ d[keys[-1] - 1] = d[1] = 1
426
+ else:
427
+ d[keys[-2]] -= 1
428
+ left = d[1] + keys[-2]
429
+ new = keys[-2]
430
+ d[1] = 0
431
+ while left:
432
+ new -= 1
433
+ if left - new >= 0:
434
+ d[new] += left//new
435
+ left -= d[new]*new
436
+ return IntegerPartition(self.integer, d)
437
+
438
+ def next_lex(self):
439
+ """Return the next partition of the integer, n, in lexical order,
440
+ wrapping around to [n] if the partition is [1, ..., 1].
441
+
442
+ Examples
443
+ ========
444
+
445
+ >>> from sympy.combinatorics.partitions import IntegerPartition
446
+ >>> p = IntegerPartition([3, 1])
447
+ >>> print(p.next_lex())
448
+ [4]
449
+ >>> p.partition < p.next_lex().partition
450
+ True
451
+ """
452
+ d = defaultdict(int)
453
+ d.update(self.as_dict())
454
+ key = self._keys
455
+ a = key[-1]
456
+ if a == self.integer:
457
+ d.clear()
458
+ d[1] = self.integer
459
+ elif a == 1:
460
+ if d[a] > 1:
461
+ d[a + 1] += 1
462
+ d[a] -= 2
463
+ else:
464
+ b = key[-2]
465
+ d[b + 1] += 1
466
+ d[1] = (d[b] - 1)*b
467
+ d[b] = 0
468
+ else:
469
+ if d[a] > 1:
470
+ if len(key) == 1:
471
+ d.clear()
472
+ d[a + 1] = 1
473
+ d[1] = self.integer - a - 1
474
+ else:
475
+ a1 = a + 1
476
+ d[a1] += 1
477
+ d[1] = d[a]*a - a1
478
+ d[a] = 0
479
+ else:
480
+ b = key[-2]
481
+ b1 = b + 1
482
+ d[b1] += 1
483
+ need = d[b]*b + d[a]*a - b1
484
+ d[a] = d[b] = 0
485
+ d[1] = need
486
+ return IntegerPartition(self.integer, d)
487
+
488
+ def as_dict(self):
489
+ """Return the partition as a dictionary whose keys are the
490
+ partition integers and the values are the multiplicity of that
491
+ integer.
492
+
493
+ Examples
494
+ ========
495
+
496
+ >>> from sympy.combinatorics.partitions import IntegerPartition
497
+ >>> IntegerPartition([1]*3 + [2] + [3]*4).as_dict()
498
+ {1: 3, 2: 1, 3: 4}
499
+ """
500
+ if self._dict is None:
501
+ groups = group(self.partition, multiple=False)
502
+ self._keys = [g[0] for g in groups]
503
+ self._dict = dict(groups)
504
+ return self._dict
505
+
506
+ @property
507
+ def conjugate(self):
508
+ """
509
+ Computes the conjugate partition of itself.
510
+
511
+ Examples
512
+ ========
513
+
514
+ >>> from sympy.combinatorics.partitions import IntegerPartition
515
+ >>> a = IntegerPartition([6, 3, 3, 2, 1])
516
+ >>> a.conjugate
517
+ [5, 4, 3, 1, 1, 1]
518
+ """
519
+ j = 1
520
+ temp_arr = list(self.partition) + [0]
521
+ k = temp_arr[0]
522
+ b = [0]*k
523
+ while k > 0:
524
+ while k > temp_arr[j]:
525
+ b[k - 1] = j
526
+ k -= 1
527
+ j += 1
528
+ return b
529
+
530
+ def __lt__(self, other):
531
+ """Return True if self is less than other when the partition
532
+ is listed from smallest to biggest.
533
+
534
+ Examples
535
+ ========
536
+
537
+ >>> from sympy.combinatorics.partitions import IntegerPartition
538
+ >>> a = IntegerPartition([3, 1])
539
+ >>> a < a
540
+ False
541
+ >>> b = a.next_lex()
542
+ >>> a < b
543
+ True
544
+ >>> a == b
545
+ False
546
+ """
547
+ return list(reversed(self.partition)) < list(reversed(other.partition))
548
+
549
+ def __le__(self, other):
550
+ """Return True if self is less than other when the partition
551
+ is listed from smallest to biggest.
552
+
553
+ Examples
554
+ ========
555
+
556
+ >>> from sympy.combinatorics.partitions import IntegerPartition
557
+ >>> a = IntegerPartition([4])
558
+ >>> a <= a
559
+ True
560
+ """
561
+ return list(reversed(self.partition)) <= list(reversed(other.partition))
562
+
563
+ def as_ferrers(self, char='#'):
564
+ """
565
+ Prints the ferrer diagram of a partition.
566
+
567
+ Examples
568
+ ========
569
+
570
+ >>> from sympy.combinatorics.partitions import IntegerPartition
571
+ >>> print(IntegerPartition([1, 1, 5]).as_ferrers())
572
+ #####
573
+ #
574
+ #
575
+ """
576
+ return "\n".join([char*i for i in self.partition])
577
+
578
+ def __str__(self):
579
+ return str(list(self.partition))
580
+
581
+
582
+ def random_integer_partition(n, seed=None):
583
+ """
584
+ Generates a random integer partition summing to ``n`` as a list
585
+ of reverse-sorted integers.
586
+
587
+ Examples
588
+ ========
589
+
590
+ >>> from sympy.combinatorics.partitions import random_integer_partition
591
+
592
+ For the following, a seed is given so a known value can be shown; in
593
+ practice, the seed would not be given.
594
+
595
+ >>> random_integer_partition(100, seed=[1, 1, 12, 1, 2, 1, 85, 1])
596
+ [85, 12, 2, 1]
597
+ >>> random_integer_partition(10, seed=[1, 2, 3, 1, 5, 1])
598
+ [5, 3, 1, 1]
599
+ >>> random_integer_partition(1)
600
+ [1]
601
+ """
602
+ from sympy.core.random import _randint
603
+
604
+ n = as_int(n)
605
+ if n < 1:
606
+ raise ValueError('n must be a positive integer')
607
+
608
+ randint = _randint(seed)
609
+
610
+ partition = []
611
+ while (n > 0):
612
+ k = randint(1, n)
613
+ mult = randint(1, n//k)
614
+ partition.append((k, mult))
615
+ n -= k*mult
616
+ partition.sort(reverse=True)
617
+ partition = flatten([[k]*m for k, m in partition])
618
+ return partition
619
+
620
+
621
+ def RGS_generalized(m):
622
+ """
623
+ Computes the m + 1 generalized unrestricted growth strings
624
+ and returns them as rows in matrix.
625
+
626
+ Examples
627
+ ========
628
+
629
+ >>> from sympy.combinatorics.partitions import RGS_generalized
630
+ >>> RGS_generalized(6)
631
+ Matrix([
632
+ [ 1, 1, 1, 1, 1, 1, 1],
633
+ [ 1, 2, 3, 4, 5, 6, 0],
634
+ [ 2, 5, 10, 17, 26, 0, 0],
635
+ [ 5, 15, 37, 77, 0, 0, 0],
636
+ [ 15, 52, 151, 0, 0, 0, 0],
637
+ [ 52, 203, 0, 0, 0, 0, 0],
638
+ [203, 0, 0, 0, 0, 0, 0]])
639
+ """
640
+ d = zeros(m + 1)
641
+ for i in range(m + 1):
642
+ d[0, i] = 1
643
+
644
+ for i in range(1, m + 1):
645
+ for j in range(m):
646
+ if j <= m - i:
647
+ d[i, j] = j * d[i - 1, j] + d[i - 1, j + 1]
648
+ else:
649
+ d[i, j] = 0
650
+ return d
651
+
652
+
653
+ def RGS_enum(m):
654
+ """
655
+ RGS_enum computes the total number of restricted growth strings
656
+ possible for a superset of size m.
657
+
658
+ Examples
659
+ ========
660
+
661
+ >>> from sympy.combinatorics.partitions import RGS_enum
662
+ >>> from sympy.combinatorics import Partition
663
+ >>> RGS_enum(4)
664
+ 15
665
+ >>> RGS_enum(5)
666
+ 52
667
+ >>> RGS_enum(6)
668
+ 203
669
+
670
+ We can check that the enumeration is correct by actually generating
671
+ the partitions. Here, the 15 partitions of 4 items are generated:
672
+
673
+ >>> a = Partition(list(range(4)))
674
+ >>> s = set()
675
+ >>> for i in range(20):
676
+ ... s.add(a)
677
+ ... a += 1
678
+ ...
679
+ >>> assert len(s) == 15
680
+
681
+ """
682
+ if (m < 1):
683
+ return 0
684
+ elif (m == 1):
685
+ return 1
686
+ else:
687
+ return bell(m)
688
+
689
+
690
+ def RGS_unrank(rank, m):
691
+ """
692
+ Gives the unranked restricted growth string for a given
693
+ superset size.
694
+
695
+ Examples
696
+ ========
697
+
698
+ >>> from sympy.combinatorics.partitions import RGS_unrank
699
+ >>> RGS_unrank(14, 4)
700
+ [0, 1, 2, 3]
701
+ >>> RGS_unrank(0, 4)
702
+ [0, 0, 0, 0]
703
+ """
704
+ if m < 1:
705
+ raise ValueError("The superset size must be >= 1")
706
+ if rank < 0 or RGS_enum(m) <= rank:
707
+ raise ValueError("Invalid arguments")
708
+
709
+ L = [1] * (m + 1)
710
+ j = 1
711
+ D = RGS_generalized(m)
712
+ for i in range(2, m + 1):
713
+ v = D[m - i, j]
714
+ cr = j*v
715
+ if cr <= rank:
716
+ L[i] = j + 1
717
+ rank -= cr
718
+ j += 1
719
+ else:
720
+ L[i] = int(rank / v + 1)
721
+ rank %= v
722
+ return [x - 1 for x in L[1:]]
723
+
724
+
725
+ def RGS_rank(rgs):
726
+ """
727
+ Computes the rank of a restricted growth string.
728
+
729
+ Examples
730
+ ========
731
+
732
+ >>> from sympy.combinatorics.partitions import RGS_rank, RGS_unrank
733
+ >>> RGS_rank([0, 1, 2, 1, 3])
734
+ 42
735
+ >>> RGS_rank(RGS_unrank(4, 7))
736
+ 4
737
+ """
738
+ rgs_size = len(rgs)
739
+ rank = 0
740
+ D = RGS_generalized(rgs_size)
741
+ for i in range(1, rgs_size):
742
+ n = len(rgs[(i + 1):])
743
+ m = max(rgs[0:i])
744
+ rank += D[n, m + 1] * rgs[i]
745
+ return rank
.venv/lib/python3.13/site-packages/sympy/combinatorics/pc_groups.py ADDED
@@ -0,0 +1,710 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sympy.ntheory.primetest import isprime
2
+ from sympy.combinatorics.perm_groups import PermutationGroup
3
+ from sympy.printing.defaults import DefaultPrinting
4
+ from sympy.combinatorics.free_groups import free_group
5
+
6
+
7
+ class PolycyclicGroup(DefaultPrinting):
8
+
9
+ is_group = True
10
+ is_solvable = True
11
+
12
+ def __init__(self, pc_sequence, pc_series, relative_order, collector=None):
13
+ """
14
+
15
+ Parameters
16
+ ==========
17
+
18
+ pc_sequence : list
19
+ A sequence of elements whose classes generate the cyclic factor
20
+ groups of pc_series.
21
+ pc_series : list
22
+ A subnormal sequence of subgroups where each factor group is cyclic.
23
+ relative_order : list
24
+ The orders of factor groups of pc_series.
25
+ collector : Collector
26
+ By default, it is None. Collector class provides the
27
+ polycyclic presentation with various other functionalities.
28
+
29
+ """
30
+ self.pcgs = pc_sequence
31
+ self.pc_series = pc_series
32
+ self.relative_order = relative_order
33
+ self.collector = Collector(self.pcgs, pc_series, relative_order) if not collector else collector
34
+
35
+ def is_prime_order(self):
36
+ return all(isprime(order) for order in self.relative_order)
37
+
38
+ def length(self):
39
+ return len(self.pcgs)
40
+
41
+
42
+ class Collector(DefaultPrinting):
43
+
44
+ """
45
+ References
46
+ ==========
47
+
48
+ .. [1] Holt, D., Eick, B., O'Brien, E.
49
+ "Handbook of Computational Group Theory"
50
+ Section 8.1.3
51
+ """
52
+
53
+ def __init__(self, pcgs, pc_series, relative_order, free_group_=None, pc_presentation=None):
54
+ """
55
+
56
+ Most of the parameters for the Collector class are the same as for PolycyclicGroup.
57
+ Others are described below.
58
+
59
+ Parameters
60
+ ==========
61
+
62
+ free_group_ : tuple
63
+ free_group_ provides the mapping of polycyclic generating
64
+ sequence with the free group elements.
65
+ pc_presentation : dict
66
+ Provides the presentation of polycyclic groups with the
67
+ help of power and conjugate relators.
68
+
69
+ See Also
70
+ ========
71
+
72
+ PolycyclicGroup
73
+
74
+ """
75
+ self.pcgs = pcgs
76
+ self.pc_series = pc_series
77
+ self.relative_order = relative_order
78
+ self.free_group = free_group('x:{}'.format(len(pcgs)))[0] if not free_group_ else free_group_
79
+ self.index = {s: i for i, s in enumerate(self.free_group.symbols)}
80
+ self.pc_presentation = self.pc_relators()
81
+
82
+ def minimal_uncollected_subword(self, word):
83
+ r"""
84
+ Returns the minimal uncollected subwords.
85
+
86
+ Explanation
87
+ ===========
88
+
89
+ A word ``v`` defined on generators in ``X`` is a minimal
90
+ uncollected subword of the word ``w`` if ``v`` is a subword
91
+ of ``w`` and it has one of the following form
92
+
93
+ * `v = {x_{i+1}}^{a_j}x_i`
94
+
95
+ * `v = {x_{i+1}}^{a_j}{x_i}^{-1}`
96
+
97
+ * `v = {x_i}^{a_j}`
98
+
99
+ for `a_j` not in `\{1, \ldots, s-1\}`. Where, ``s`` is the power
100
+ exponent of the corresponding generator.
101
+
102
+ Examples
103
+ ========
104
+
105
+ >>> from sympy.combinatorics.named_groups import SymmetricGroup
106
+ >>> from sympy.combinatorics import free_group
107
+ >>> G = SymmetricGroup(4)
108
+ >>> PcGroup = G.polycyclic_group()
109
+ >>> collector = PcGroup.collector
110
+ >>> F, x1, x2 = free_group("x1, x2")
111
+ >>> word = x2**2*x1**7
112
+ >>> collector.minimal_uncollected_subword(word)
113
+ ((x2, 2),)
114
+
115
+ """
116
+ # To handle the case word = <identity>
117
+ if not word:
118
+ return None
119
+
120
+ array = word.array_form
121
+ re = self.relative_order
122
+ index = self.index
123
+
124
+ for i in range(len(array)):
125
+ s1, e1 = array[i]
126
+
127
+ if re[index[s1]] and (e1 < 0 or e1 > re[index[s1]]-1):
128
+ return ((s1, e1), )
129
+
130
+ for i in range(len(array)-1):
131
+ s1, e1 = array[i]
132
+ s2, e2 = array[i+1]
133
+
134
+ if index[s1] > index[s2]:
135
+ e = 1 if e2 > 0 else -1
136
+ return ((s1, e1), (s2, e))
137
+
138
+ return None
139
+
140
+ def relations(self):
141
+ """
142
+ Separates the given relators of pc presentation in power and
143
+ conjugate relations.
144
+
145
+ Returns
146
+ =======
147
+
148
+ (power_rel, conj_rel)
149
+ Separates pc presentation into power and conjugate relations.
150
+
151
+ Examples
152
+ ========
153
+
154
+ >>> from sympy.combinatorics.named_groups import SymmetricGroup
155
+ >>> G = SymmetricGroup(3)
156
+ >>> PcGroup = G.polycyclic_group()
157
+ >>> collector = PcGroup.collector
158
+ >>> power_rel, conj_rel = collector.relations()
159
+ >>> power_rel
160
+ {x0**2: (), x1**3: ()}
161
+ >>> conj_rel
162
+ {x0**-1*x1*x0: x1**2}
163
+
164
+ See Also
165
+ ========
166
+
167
+ pc_relators
168
+
169
+ """
170
+ power_relators = {}
171
+ conjugate_relators = {}
172
+ for key, value in self.pc_presentation.items():
173
+ if len(key.array_form) == 1:
174
+ power_relators[key] = value
175
+ else:
176
+ conjugate_relators[key] = value
177
+ return power_relators, conjugate_relators
178
+
179
+ def subword_index(self, word, w):
180
+ """
181
+ Returns the start and ending index of a given
182
+ subword in a word.
183
+
184
+ Parameters
185
+ ==========
186
+
187
+ word : FreeGroupElement
188
+ word defined on free group elements for a
189
+ polycyclic group.
190
+ w : FreeGroupElement
191
+ subword of a given word, whose starting and
192
+ ending index to be computed.
193
+
194
+ Returns
195
+ =======
196
+
197
+ (i, j)
198
+ A tuple containing starting and ending index of ``w``
199
+ in the given word. If not exists, (-1,-1) is returned.
200
+
201
+ Examples
202
+ ========
203
+
204
+ >>> from sympy.combinatorics.named_groups import SymmetricGroup
205
+ >>> from sympy.combinatorics import free_group
206
+ >>> G = SymmetricGroup(4)
207
+ >>> PcGroup = G.polycyclic_group()
208
+ >>> collector = PcGroup.collector
209
+ >>> F, x1, x2 = free_group("x1, x2")
210
+ >>> word = x2**2*x1**7
211
+ >>> w = x2**2*x1
212
+ >>> collector.subword_index(word, w)
213
+ (0, 3)
214
+ >>> w = x1**7
215
+ >>> collector.subword_index(word, w)
216
+ (2, 9)
217
+ >>> w = x1**8
218
+ >>> collector.subword_index(word, w)
219
+ (-1, -1)
220
+
221
+ """
222
+ low = -1
223
+ high = -1
224
+ for i in range(len(word)-len(w)+1):
225
+ if word.subword(i, i+len(w)) == w:
226
+ low = i
227
+ high = i+len(w)
228
+ break
229
+ return low, high
230
+
231
+ def map_relation(self, w):
232
+ """
233
+ Return a conjugate relation.
234
+
235
+ Explanation
236
+ ===========
237
+
238
+ Given a word formed by two free group elements, the
239
+ corresponding conjugate relation with those free
240
+ group elements is formed and mapped with the collected
241
+ word in the polycyclic presentation.
242
+
243
+ Examples
244
+ ========
245
+
246
+ >>> from sympy.combinatorics.named_groups import SymmetricGroup
247
+ >>> from sympy.combinatorics import free_group
248
+ >>> G = SymmetricGroup(3)
249
+ >>> PcGroup = G.polycyclic_group()
250
+ >>> collector = PcGroup.collector
251
+ >>> F, x0, x1 = free_group("x0, x1")
252
+ >>> w = x1*x0
253
+ >>> collector.map_relation(w)
254
+ x1**2
255
+
256
+ See Also
257
+ ========
258
+
259
+ pc_presentation
260
+
261
+ """
262
+ array = w.array_form
263
+ s1 = array[0][0]
264
+ s2 = array[1][0]
265
+ key = ((s2, -1), (s1, 1), (s2, 1))
266
+ key = self.free_group.dtype(key)
267
+ return self.pc_presentation[key]
268
+
269
+
270
+ def collected_word(self, word):
271
+ r"""
272
+ Return the collected form of a word.
273
+
274
+ Explanation
275
+ ===========
276
+
277
+ A word ``w`` is called collected, if `w = {x_{i_1}}^{a_1} * \ldots *
278
+ {x_{i_r}}^{a_r}` with `i_1 < i_2< \ldots < i_r` and `a_j` is in
279
+ `\{1, \ldots, {s_j}-1\}`.
280
+
281
+ Otherwise w is uncollected.
282
+
283
+ Parameters
284
+ ==========
285
+
286
+ word : FreeGroupElement
287
+ An uncollected word.
288
+
289
+ Returns
290
+ =======
291
+
292
+ word
293
+ A collected word of form `w = {x_{i_1}}^{a_1}, \ldots,
294
+ {x_{i_r}}^{a_r}` with `i_1, i_2, \ldots, i_r` and `a_j \in
295
+ \{1, \ldots, {s_j}-1\}`.
296
+
297
+ Examples
298
+ ========
299
+
300
+ >>> from sympy.combinatorics.named_groups import SymmetricGroup
301
+ >>> from sympy.combinatorics.perm_groups import PermutationGroup
302
+ >>> from sympy.combinatorics import free_group
303
+ >>> G = SymmetricGroup(4)
304
+ >>> PcGroup = G.polycyclic_group()
305
+ >>> collector = PcGroup.collector
306
+ >>> F, x0, x1, x2, x3 = free_group("x0, x1, x2, x3")
307
+ >>> word = x3*x2*x1*x0
308
+ >>> collected_word = collector.collected_word(word)
309
+ >>> free_to_perm = {}
310
+ >>> free_group = collector.free_group
311
+ >>> for sym, gen in zip(free_group.symbols, collector.pcgs):
312
+ ... free_to_perm[sym] = gen
313
+ >>> G1 = PermutationGroup()
314
+ >>> for w in word:
315
+ ... sym = w[0]
316
+ ... perm = free_to_perm[sym]
317
+ ... G1 = PermutationGroup([perm] + G1.generators)
318
+ >>> G2 = PermutationGroup()
319
+ >>> for w in collected_word:
320
+ ... sym = w[0]
321
+ ... perm = free_to_perm[sym]
322
+ ... G2 = PermutationGroup([perm] + G2.generators)
323
+
324
+ The two are not identical, but they are equivalent:
325
+
326
+ >>> G1.equals(G2), G1 == G2
327
+ (True, False)
328
+
329
+ See Also
330
+ ========
331
+
332
+ minimal_uncollected_subword
333
+
334
+ """
335
+ free_group = self.free_group
336
+ while True:
337
+ w = self.minimal_uncollected_subword(word)
338
+ if not w:
339
+ break
340
+
341
+ low, high = self.subword_index(word, free_group.dtype(w))
342
+ if low == -1:
343
+ continue
344
+
345
+ s1, e1 = w[0]
346
+ if len(w) == 1:
347
+ re = self.relative_order[self.index[s1]]
348
+ q = e1 // re
349
+ r = e1-q*re
350
+
351
+ key = ((w[0][0], re), )
352
+ key = free_group.dtype(key)
353
+ if self.pc_presentation[key]:
354
+ presentation = self.pc_presentation[key].array_form
355
+ sym, exp = presentation[0]
356
+ word_ = ((w[0][0], r), (sym, q*exp))
357
+ word_ = free_group.dtype(word_)
358
+ else:
359
+ if r != 0:
360
+ word_ = ((w[0][0], r), )
361
+ word_ = free_group.dtype(word_)
362
+ else:
363
+ word_ = None
364
+ word = word.eliminate_word(free_group.dtype(w), word_)
365
+
366
+ if len(w) == 2 and w[1][1] > 0:
367
+ s2, e2 = w[1]
368
+ s2 = ((s2, 1), )
369
+ s2 = free_group.dtype(s2)
370
+ word_ = self.map_relation(free_group.dtype(w))
371
+ word_ = s2*word_**e1
372
+ word_ = free_group.dtype(word_)
373
+ word = word.substituted_word(low, high, word_)
374
+
375
+ elif len(w) == 2 and w[1][1] < 0:
376
+ s2, e2 = w[1]
377
+ s2 = ((s2, 1), )
378
+ s2 = free_group.dtype(s2)
379
+ word_ = self.map_relation(free_group.dtype(w))
380
+ word_ = s2**-1*word_**e1
381
+ word_ = free_group.dtype(word_)
382
+ word = word.substituted_word(low, high, word_)
383
+
384
+ return word
385
+
386
+
387
+ def pc_relators(self):
388
+ r"""
389
+ Return the polycyclic presentation.
390
+
391
+ Explanation
392
+ ===========
393
+
394
+ There are two types of relations used in polycyclic
395
+ presentation.
396
+
397
+ * Power relations : Power relators are of the form `x_i^{re_i}`,
398
+ where `i \in \{0, \ldots, \mathrm{len(pcgs)}\}`, ``x`` represents polycyclic
399
+ generator and ``re`` is the corresponding relative order.
400
+
401
+ * Conjugate relations : Conjugate relators are of the form `x_j^-1x_ix_j`,
402
+ where `j < i \in \{0, \ldots, \mathrm{len(pcgs)}\}`.
403
+
404
+ Returns
405
+ =======
406
+
407
+ A dictionary with power and conjugate relations as key and
408
+ their collected form as corresponding values.
409
+
410
+ Notes
411
+ =====
412
+
413
+ Identity Permutation is mapped with empty ``()``.
414
+
415
+ Examples
416
+ ========
417
+
418
+ >>> from sympy.combinatorics.named_groups import SymmetricGroup
419
+ >>> from sympy.combinatorics.permutations import Permutation
420
+ >>> S = SymmetricGroup(49).sylow_subgroup(7)
421
+ >>> der = S.derived_series()
422
+ >>> G = der[len(der)-2]
423
+ >>> PcGroup = G.polycyclic_group()
424
+ >>> collector = PcGroup.collector
425
+ >>> pcgs = PcGroup.pcgs
426
+ >>> len(pcgs)
427
+ 6
428
+ >>> free_group = collector.free_group
429
+ >>> pc_resentation = collector.pc_presentation
430
+ >>> free_to_perm = {}
431
+ >>> for s, g in zip(free_group.symbols, pcgs):
432
+ ... free_to_perm[s] = g
433
+
434
+ >>> for k, v in pc_resentation.items():
435
+ ... k_array = k.array_form
436
+ ... if v != ():
437
+ ... v_array = v.array_form
438
+ ... lhs = Permutation()
439
+ ... for gen in k_array:
440
+ ... s = gen[0]
441
+ ... e = gen[1]
442
+ ... lhs = lhs*free_to_perm[s]**e
443
+ ... if v == ():
444
+ ... assert lhs.is_identity
445
+ ... continue
446
+ ... rhs = Permutation()
447
+ ... for gen in v_array:
448
+ ... s = gen[0]
449
+ ... e = gen[1]
450
+ ... rhs = rhs*free_to_perm[s]**e
451
+ ... assert lhs == rhs
452
+
453
+ """
454
+ free_group = self.free_group
455
+ rel_order = self.relative_order
456
+ pc_relators = {}
457
+ perm_to_free = {}
458
+ pcgs = self.pcgs
459
+
460
+ for gen, s in zip(pcgs, free_group.generators):
461
+ perm_to_free[gen**-1] = s**-1
462
+ perm_to_free[gen] = s
463
+
464
+ pcgs = pcgs[::-1]
465
+ series = self.pc_series[::-1]
466
+ rel_order = rel_order[::-1]
467
+ collected_gens = []
468
+
469
+ for i, gen in enumerate(pcgs):
470
+ re = rel_order[i]
471
+ relation = perm_to_free[gen]**re
472
+ G = series[i]
473
+
474
+ l = G.generator_product(gen**re, original = True)
475
+ l.reverse()
476
+
477
+ word = free_group.identity
478
+ for g in l:
479
+ word = word*perm_to_free[g]
480
+
481
+ word = self.collected_word(word)
482
+ pc_relators[relation] = word if word else ()
483
+ self.pc_presentation = pc_relators
484
+
485
+ collected_gens.append(gen)
486
+ if len(collected_gens) > 1:
487
+ conj = collected_gens[len(collected_gens)-1]
488
+ conjugator = perm_to_free[conj]
489
+
490
+ for j in range(len(collected_gens)-1):
491
+ conjugated = perm_to_free[collected_gens[j]]
492
+
493
+ relation = conjugator**-1*conjugated*conjugator
494
+ gens = conj**-1*collected_gens[j]*conj
495
+
496
+ l = G.generator_product(gens, original = True)
497
+ l.reverse()
498
+ word = free_group.identity
499
+ for g in l:
500
+ word = word*perm_to_free[g]
501
+
502
+ word = self.collected_word(word)
503
+ pc_relators[relation] = word if word else ()
504
+ self.pc_presentation = pc_relators
505
+
506
+ return pc_relators
507
+
508
+ def exponent_vector(self, element):
509
+ r"""
510
+ Return the exponent vector of length equal to the
511
+ length of polycyclic generating sequence.
512
+
513
+ Explanation
514
+ ===========
515
+
516
+ For a given generator/element ``g`` of the polycyclic group,
517
+ it can be represented as `g = {x_1}^{e_1}, \ldots, {x_n}^{e_n}`,
518
+ where `x_i` represents polycyclic generators and ``n`` is
519
+ the number of generators in the free_group equal to the length
520
+ of pcgs.
521
+
522
+ Parameters
523
+ ==========
524
+
525
+ element : Permutation
526
+ Generator of a polycyclic group.
527
+
528
+ Examples
529
+ ========
530
+
531
+ >>> from sympy.combinatorics.named_groups import SymmetricGroup
532
+ >>> from sympy.combinatorics.permutations import Permutation
533
+ >>> G = SymmetricGroup(4)
534
+ >>> PcGroup = G.polycyclic_group()
535
+ >>> collector = PcGroup.collector
536
+ >>> pcgs = PcGroup.pcgs
537
+ >>> collector.exponent_vector(G[0])
538
+ [1, 0, 0, 0]
539
+ >>> exp = collector.exponent_vector(G[1])
540
+ >>> g = Permutation()
541
+ >>> for i in range(len(exp)):
542
+ ... g = g*pcgs[i]**exp[i] if exp[i] else g
543
+ >>> assert g == G[1]
544
+
545
+ References
546
+ ==========
547
+
548
+ .. [1] Holt, D., Eick, B., O'Brien, E.
549
+ "Handbook of Computational Group Theory"
550
+ Section 8.1.1, Definition 8.4
551
+
552
+ """
553
+ free_group = self.free_group
554
+ G = PermutationGroup()
555
+ for g in self.pcgs:
556
+ G = PermutationGroup([g] + G.generators)
557
+ gens = G.generator_product(element, original = True)
558
+ gens.reverse()
559
+
560
+ perm_to_free = {}
561
+ for sym, g in zip(free_group.generators, self.pcgs):
562
+ perm_to_free[g**-1] = sym**-1
563
+ perm_to_free[g] = sym
564
+ w = free_group.identity
565
+ for g in gens:
566
+ w = w*perm_to_free[g]
567
+
568
+ word = self.collected_word(w)
569
+
570
+ index = self.index
571
+ exp_vector = [0]*len(free_group)
572
+ word = word.array_form
573
+ for t in word:
574
+ exp_vector[index[t[0]]] = t[1]
575
+ return exp_vector
576
+
577
+ def depth(self, element):
578
+ r"""
579
+ Return the depth of a given element.
580
+
581
+ Explanation
582
+ ===========
583
+
584
+ The depth of a given element ``g`` is defined by
585
+ `\mathrm{dep}[g] = i` if `e_1 = e_2 = \ldots = e_{i-1} = 0`
586
+ and `e_i != 0`, where ``e`` represents the exponent-vector.
587
+
588
+ Examples
589
+ ========
590
+
591
+ >>> from sympy.combinatorics.named_groups import SymmetricGroup
592
+ >>> G = SymmetricGroup(3)
593
+ >>> PcGroup = G.polycyclic_group()
594
+ >>> collector = PcGroup.collector
595
+ >>> collector.depth(G[0])
596
+ 2
597
+ >>> collector.depth(G[1])
598
+ 1
599
+
600
+ References
601
+ ==========
602
+
603
+ .. [1] Holt, D., Eick, B., O'Brien, E.
604
+ "Handbook of Computational Group Theory"
605
+ Section 8.1.1, Definition 8.5
606
+
607
+ """
608
+ exp_vector = self.exponent_vector(element)
609
+ return next((i+1 for i, x in enumerate(exp_vector) if x), len(self.pcgs)+1)
610
+
611
+ def leading_exponent(self, element):
612
+ r"""
613
+ Return the leading non-zero exponent.
614
+
615
+ Explanation
616
+ ===========
617
+
618
+ The leading exponent for a given element `g` is defined
619
+ by `\mathrm{leading\_exponent}[g]` `= e_i`, if `\mathrm{depth}[g] = i`.
620
+
621
+ Examples
622
+ ========
623
+
624
+ >>> from sympy.combinatorics.named_groups import SymmetricGroup
625
+ >>> G = SymmetricGroup(3)
626
+ >>> PcGroup = G.polycyclic_group()
627
+ >>> collector = PcGroup.collector
628
+ >>> collector.leading_exponent(G[1])
629
+ 1
630
+
631
+ """
632
+ exp_vector = self.exponent_vector(element)
633
+ depth = self.depth(element)
634
+ if depth != len(self.pcgs)+1:
635
+ return exp_vector[depth-1]
636
+ return None
637
+
638
+ def _sift(self, z, g):
639
+ h = g
640
+ d = self.depth(h)
641
+ while d < len(self.pcgs) and z[d-1] != 1:
642
+ k = z[d-1]
643
+ e = self.leading_exponent(h)*(self.leading_exponent(k))**-1
644
+ e = e % self.relative_order[d-1]
645
+ h = k**-e*h
646
+ d = self.depth(h)
647
+ return h
648
+
649
+ def induced_pcgs(self, gens):
650
+ """
651
+
652
+ Parameters
653
+ ==========
654
+
655
+ gens : list
656
+ A list of generators on which polycyclic subgroup
657
+ is to be defined.
658
+
659
+ Examples
660
+ ========
661
+
662
+ >>> from sympy.combinatorics.named_groups import SymmetricGroup
663
+ >>> S = SymmetricGroup(8)
664
+ >>> G = S.sylow_subgroup(2)
665
+ >>> PcGroup = G.polycyclic_group()
666
+ >>> collector = PcGroup.collector
667
+ >>> gens = [G[0], G[1]]
668
+ >>> ipcgs = collector.induced_pcgs(gens)
669
+ >>> [gen.order() for gen in ipcgs]
670
+ [2, 2, 2]
671
+ >>> G = S.sylow_subgroup(3)
672
+ >>> PcGroup = G.polycyclic_group()
673
+ >>> collector = PcGroup.collector
674
+ >>> gens = [G[0], G[1]]
675
+ >>> ipcgs = collector.induced_pcgs(gens)
676
+ >>> [gen.order() for gen in ipcgs]
677
+ [3]
678
+
679
+ """
680
+ z = [1]*len(self.pcgs)
681
+ G = gens
682
+ while G:
683
+ g = G.pop(0)
684
+ h = self._sift(z, g)
685
+ d = self.depth(h)
686
+ if d < len(self.pcgs):
687
+ for gen in z:
688
+ if gen != 1:
689
+ G.append(h**-1*gen**-1*h*gen)
690
+ z[d-1] = h
691
+ z = [gen for gen in z if gen != 1]
692
+ return z
693
+
694
+ def constructive_membership_test(self, ipcgs, g):
695
+ """
696
+ Return the exponent vector for induced pcgs.
697
+ """
698
+ e = [0]*len(ipcgs)
699
+ h = g
700
+ d = self.depth(h)
701
+ for i, gen in enumerate(ipcgs):
702
+ while self.depth(gen) == d:
703
+ f = self.leading_exponent(h)*self.leading_exponent(gen)
704
+ f = f % self.relative_order[d-1]
705
+ h = gen**(-f)*h
706
+ e[i] = f
707
+ d = self.depth(h)
708
+ if h == 1:
709
+ return e
710
+ return False
.venv/lib/python3.13/site-packages/sympy/combinatorics/perm_groups.py ADDED
The diff for this file is too large to render. See raw diff
 
.venv/lib/python3.13/site-packages/sympy/combinatorics/permutations.py ADDED
@@ -0,0 +1,3114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import random
2
+ from collections import defaultdict
3
+ from collections.abc import Iterable
4
+ from functools import reduce
5
+
6
+ from sympy.core.parameters import global_parameters
7
+ from sympy.core.basic import Atom
8
+ from sympy.core.expr import Expr
9
+ from sympy.core.numbers import int_valued
10
+ from sympy.core.numbers import Integer
11
+ from sympy.core.sympify import _sympify
12
+ from sympy.matrices import zeros
13
+ from sympy.polys.polytools import lcm
14
+ from sympy.printing.repr import srepr
15
+ from sympy.utilities.iterables import (flatten, has_variety, minlex,
16
+ has_dups, runs, is_sequence)
17
+ from sympy.utilities.misc import as_int
18
+ from mpmath.libmp.libintmath import ifac
19
+ from sympy.multipledispatch import dispatch
20
+
21
+ def _af_rmul(a, b):
22
+ """
23
+ Return the product b*a; input and output are array forms. The ith value
24
+ is a[b[i]].
25
+
26
+ Examples
27
+ ========
28
+
29
+ >>> from sympy.combinatorics.permutations import _af_rmul, Permutation
30
+
31
+ >>> a, b = [1, 0, 2], [0, 2, 1]
32
+ >>> _af_rmul(a, b)
33
+ [1, 2, 0]
34
+ >>> [a[b[i]] for i in range(3)]
35
+ [1, 2, 0]
36
+
37
+ This handles the operands in reverse order compared to the ``*`` operator:
38
+
39
+ >>> a = Permutation(a)
40
+ >>> b = Permutation(b)
41
+ >>> list(a*b)
42
+ [2, 0, 1]
43
+ >>> [b(a(i)) for i in range(3)]
44
+ [2, 0, 1]
45
+
46
+ See Also
47
+ ========
48
+
49
+ rmul, _af_rmuln
50
+ """
51
+ return [a[i] for i in b]
52
+
53
+
54
+ def _af_rmuln(*abc):
55
+ """
56
+ Given [a, b, c, ...] return the product of ...*c*b*a using array forms.
57
+ The ith value is a[b[c[i]]].
58
+
59
+ Examples
60
+ ========
61
+
62
+ >>> from sympy.combinatorics.permutations import _af_rmul, Permutation
63
+
64
+ >>> a, b = [1, 0, 2], [0, 2, 1]
65
+ >>> _af_rmul(a, b)
66
+ [1, 2, 0]
67
+ >>> [a[b[i]] for i in range(3)]
68
+ [1, 2, 0]
69
+
70
+ This handles the operands in reverse order compared to the ``*`` operator:
71
+
72
+ >>> a = Permutation(a); b = Permutation(b)
73
+ >>> list(a*b)
74
+ [2, 0, 1]
75
+ >>> [b(a(i)) for i in range(3)]
76
+ [2, 0, 1]
77
+
78
+ See Also
79
+ ========
80
+
81
+ rmul, _af_rmul
82
+ """
83
+ a = abc
84
+ m = len(a)
85
+ if m == 3:
86
+ p0, p1, p2 = a
87
+ return [p0[p1[i]] for i in p2]
88
+ if m == 4:
89
+ p0, p1, p2, p3 = a
90
+ return [p0[p1[p2[i]]] for i in p3]
91
+ if m == 5:
92
+ p0, p1, p2, p3, p4 = a
93
+ return [p0[p1[p2[p3[i]]]] for i in p4]
94
+ if m == 6:
95
+ p0, p1, p2, p3, p4, p5 = a
96
+ return [p0[p1[p2[p3[p4[i]]]]] for i in p5]
97
+ if m == 7:
98
+ p0, p1, p2, p3, p4, p5, p6 = a
99
+ return [p0[p1[p2[p3[p4[p5[i]]]]]] for i in p6]
100
+ if m == 8:
101
+ p0, p1, p2, p3, p4, p5, p6, p7 = a
102
+ return [p0[p1[p2[p3[p4[p5[p6[i]]]]]]] for i in p7]
103
+ if m == 1:
104
+ return a[0][:]
105
+ if m == 2:
106
+ a, b = a
107
+ return [a[i] for i in b]
108
+ if m == 0:
109
+ raise ValueError("String must not be empty")
110
+ p0 = _af_rmuln(*a[:m//2])
111
+ p1 = _af_rmuln(*a[m//2:])
112
+ return [p0[i] for i in p1]
113
+
114
+
115
+ def _af_parity(pi):
116
+ """
117
+ Computes the parity of a permutation in array form.
118
+
119
+ Explanation
120
+ ===========
121
+
122
+ The parity of a permutation reflects the parity of the
123
+ number of inversions in the permutation, i.e., the
124
+ number of pairs of x and y such that x > y but p[x] < p[y].
125
+
126
+ Examples
127
+ ========
128
+
129
+ >>> from sympy.combinatorics.permutations import _af_parity
130
+ >>> _af_parity([0, 1, 2, 3])
131
+ 0
132
+ >>> _af_parity([3, 2, 0, 1])
133
+ 1
134
+
135
+ See Also
136
+ ========
137
+
138
+ Permutation
139
+ """
140
+ n = len(pi)
141
+ a = [0] * n
142
+ c = 0
143
+ for j in range(n):
144
+ if a[j] == 0:
145
+ c += 1
146
+ a[j] = 1
147
+ i = j
148
+ while pi[i] != j:
149
+ i = pi[i]
150
+ a[i] = 1
151
+ return (n - c) % 2
152
+
153
+
154
+ def _af_invert(a):
155
+ """
156
+ Finds the inverse, ~A, of a permutation, A, given in array form.
157
+
158
+ Examples
159
+ ========
160
+
161
+ >>> from sympy.combinatorics.permutations import _af_invert, _af_rmul
162
+ >>> A = [1, 2, 0, 3]
163
+ >>> _af_invert(A)
164
+ [2, 0, 1, 3]
165
+ >>> _af_rmul(_, A)
166
+ [0, 1, 2, 3]
167
+
168
+ See Also
169
+ ========
170
+
171
+ Permutation, __invert__
172
+ """
173
+ inv_form = [0] * len(a)
174
+ for i, ai in enumerate(a):
175
+ inv_form[ai] = i
176
+ return inv_form
177
+
178
+
179
+ def _af_pow(a, n):
180
+ """
181
+ Routine for finding powers of a permutation.
182
+
183
+ Examples
184
+ ========
185
+
186
+ >>> from sympy.combinatorics import Permutation
187
+ >>> from sympy.combinatorics.permutations import _af_pow
188
+ >>> p = Permutation([2, 0, 3, 1])
189
+ >>> p.order()
190
+ 4
191
+ >>> _af_pow(p._array_form, 4)
192
+ [0, 1, 2, 3]
193
+ """
194
+ if n == 0:
195
+ return list(range(len(a)))
196
+ if n < 0:
197
+ return _af_pow(_af_invert(a), -n)
198
+ if n == 1:
199
+ return a[:]
200
+ elif n == 2:
201
+ b = [a[i] for i in a]
202
+ elif n == 3:
203
+ b = [a[a[i]] for i in a]
204
+ elif n == 4:
205
+ b = [a[a[a[i]]] for i in a]
206
+ else:
207
+ # use binary multiplication
208
+ b = list(range(len(a)))
209
+ while 1:
210
+ if n & 1:
211
+ b = [b[i] for i in a]
212
+ n -= 1
213
+ if not n:
214
+ break
215
+ if n % 4 == 0:
216
+ a = [a[a[a[i]]] for i in a]
217
+ n = n // 4
218
+ elif n % 2 == 0:
219
+ a = [a[i] for i in a]
220
+ n = n // 2
221
+ return b
222
+
223
+
224
+ def _af_commutes_with(a, b):
225
+ """
226
+ Checks if the two permutations with array forms
227
+ given by ``a`` and ``b`` commute.
228
+
229
+ Examples
230
+ ========
231
+
232
+ >>> from sympy.combinatorics.permutations import _af_commutes_with
233
+ >>> _af_commutes_with([1, 2, 0], [0, 2, 1])
234
+ False
235
+
236
+ See Also
237
+ ========
238
+
239
+ Permutation, commutes_with
240
+ """
241
+ return not any(a[b[i]] != b[a[i]] for i in range(len(a) - 1))
242
+
243
+
244
+ class Cycle(dict):
245
+ """
246
+ Wrapper around dict which provides the functionality of a disjoint cycle.
247
+
248
+ Explanation
249
+ ===========
250
+
251
+ A cycle shows the rule to use to move subsets of elements to obtain
252
+ a permutation. The Cycle class is more flexible than Permutation in
253
+ that 1) all elements need not be present in order to investigate how
254
+ multiple cycles act in sequence and 2) it can contain singletons:
255
+
256
+ >>> from sympy.combinatorics.permutations import Perm, Cycle
257
+
258
+ A Cycle will automatically parse a cycle given as a tuple on the rhs:
259
+
260
+ >>> Cycle(1, 2)(2, 3)
261
+ (1 3 2)
262
+
263
+ The identity cycle, Cycle(), can be used to start a product:
264
+
265
+ >>> Cycle()(1, 2)(2, 3)
266
+ (1 3 2)
267
+
268
+ The array form of a Cycle can be obtained by calling the list
269
+ method (or passing it to the list function) and all elements from
270
+ 0 will be shown:
271
+
272
+ >>> a = Cycle(1, 2)
273
+ >>> a.list()
274
+ [0, 2, 1]
275
+ >>> list(a)
276
+ [0, 2, 1]
277
+
278
+ If a larger (or smaller) range is desired use the list method and
279
+ provide the desired size -- but the Cycle cannot be truncated to
280
+ a size smaller than the largest element that is out of place:
281
+
282
+ >>> b = Cycle(2, 4)(1, 2)(3, 1, 4)(1, 3)
283
+ >>> b.list()
284
+ [0, 2, 1, 3, 4]
285
+ >>> b.list(b.size + 1)
286
+ [0, 2, 1, 3, 4, 5]
287
+ >>> b.list(-1)
288
+ [0, 2, 1]
289
+
290
+ Singletons are not shown when printing with one exception: the largest
291
+ element is always shown -- as a singleton if necessary:
292
+
293
+ >>> Cycle(1, 4, 10)(4, 5)
294
+ (1 5 4 10)
295
+ >>> Cycle(1, 2)(4)(5)(10)
296
+ (1 2)(10)
297
+
298
+ The array form can be used to instantiate a Permutation so other
299
+ properties of the permutation can be investigated:
300
+
301
+ >>> Perm(Cycle(1, 2)(3, 4).list()).transpositions()
302
+ [(1, 2), (3, 4)]
303
+
304
+ Notes
305
+ =====
306
+
307
+ The underlying structure of the Cycle is a dictionary and although
308
+ the __iter__ method has been redefined to give the array form of the
309
+ cycle, the underlying dictionary items are still available with the
310
+ such methods as items():
311
+
312
+ >>> list(Cycle(1, 2).items())
313
+ [(1, 2), (2, 1)]
314
+
315
+ See Also
316
+ ========
317
+
318
+ Permutation
319
+ """
320
+ def __missing__(self, arg):
321
+ """Enter arg into dictionary and return arg."""
322
+ return as_int(arg)
323
+
324
+ def __iter__(self):
325
+ yield from self.list()
326
+
327
+ def __call__(self, *other):
328
+ """Return product of cycles processed from R to L.
329
+
330
+ Examples
331
+ ========
332
+
333
+ >>> from sympy.combinatorics import Cycle
334
+ >>> Cycle(1, 2)(2, 3)
335
+ (1 3 2)
336
+
337
+ An instance of a Cycle will automatically parse list-like
338
+ objects and Permutations that are on the right. It is more
339
+ flexible than the Permutation in that all elements need not
340
+ be present:
341
+
342
+ >>> a = Cycle(1, 2)
343
+ >>> a(2, 3)
344
+ (1 3 2)
345
+ >>> a(2, 3)(4, 5)
346
+ (1 3 2)(4 5)
347
+
348
+ """
349
+ rv = Cycle(*other)
350
+ for k, v in zip(list(self.keys()), [rv[self[k]] for k in self.keys()]):
351
+ rv[k] = v
352
+ return rv
353
+
354
+ def list(self, size=None):
355
+ """Return the cycles as an explicit list starting from 0 up
356
+ to the greater of the largest value in the cycles and size.
357
+
358
+ Truncation of trailing unmoved items will occur when size
359
+ is less than the maximum element in the cycle; if this is
360
+ desired, setting ``size=-1`` will guarantee such trimming.
361
+
362
+ Examples
363
+ ========
364
+
365
+ >>> from sympy.combinatorics import Cycle
366
+ >>> p = Cycle(2, 3)(4, 5)
367
+ >>> p.list()
368
+ [0, 1, 3, 2, 5, 4]
369
+ >>> p.list(10)
370
+ [0, 1, 3, 2, 5, 4, 6, 7, 8, 9]
371
+
372
+ Passing a length too small will trim trailing, unchanged elements
373
+ in the permutation:
374
+
375
+ >>> Cycle(2, 4)(1, 2, 4).list(-1)
376
+ [0, 2, 1]
377
+ """
378
+ if not self and size is None:
379
+ raise ValueError('must give size for empty Cycle')
380
+ if size is not None:
381
+ big = max([i for i in self.keys() if self[i] != i] + [0])
382
+ size = max(size, big + 1)
383
+ else:
384
+ size = self.size
385
+ return [self[i] for i in range(size)]
386
+
387
+ def __repr__(self):
388
+ """We want it to print as a Cycle, not as a dict.
389
+
390
+ Examples
391
+ ========
392
+
393
+ >>> from sympy.combinatorics import Cycle
394
+ >>> Cycle(1, 2)
395
+ (1 2)
396
+ >>> print(_)
397
+ (1 2)
398
+ >>> list(Cycle(1, 2).items())
399
+ [(1, 2), (2, 1)]
400
+ """
401
+ if not self:
402
+ return 'Cycle()'
403
+ cycles = Permutation(self).cyclic_form
404
+ s = ''.join(str(tuple(c)) for c in cycles)
405
+ big = self.size - 1
406
+ if not any(i == big for c in cycles for i in c):
407
+ s += '(%s)' % big
408
+ return 'Cycle%s' % s
409
+
410
+ def __str__(self):
411
+ """We want it to be printed in a Cycle notation with no
412
+ comma in-between.
413
+
414
+ Examples
415
+ ========
416
+
417
+ >>> from sympy.combinatorics import Cycle
418
+ >>> Cycle(1, 2)
419
+ (1 2)
420
+ >>> Cycle(1, 2, 4)(5, 6)
421
+ (1 2 4)(5 6)
422
+ """
423
+ if not self:
424
+ return '()'
425
+ cycles = Permutation(self).cyclic_form
426
+ s = ''.join(str(tuple(c)) for c in cycles)
427
+ big = self.size - 1
428
+ if not any(i == big for c in cycles for i in c):
429
+ s += '(%s)' % big
430
+ s = s.replace(',', '')
431
+ return s
432
+
433
+ def __init__(self, *args):
434
+ """Load up a Cycle instance with the values for the cycle.
435
+
436
+ Examples
437
+ ========
438
+
439
+ >>> from sympy.combinatorics import Cycle
440
+ >>> Cycle(1, 2, 6)
441
+ (1 2 6)
442
+ """
443
+
444
+ if not args:
445
+ return
446
+ if len(args) == 1:
447
+ if isinstance(args[0], Permutation):
448
+ for c in args[0].cyclic_form:
449
+ self.update(self(*c))
450
+ return
451
+ elif isinstance(args[0], Cycle):
452
+ for k, v in args[0].items():
453
+ self[k] = v
454
+ return
455
+ args = [as_int(a) for a in args]
456
+ if any(i < 0 for i in args):
457
+ raise ValueError('negative integers are not allowed in a cycle.')
458
+ if has_dups(args):
459
+ raise ValueError('All elements must be unique in a cycle.')
460
+ for i in range(-len(args), 0):
461
+ self[args[i]] = args[i + 1]
462
+
463
+ @property
464
+ def size(self):
465
+ if not self:
466
+ return 0
467
+ return max(self.keys()) + 1
468
+
469
+ def copy(self):
470
+ return Cycle(self)
471
+
472
+
473
+ class Permutation(Atom):
474
+ r"""
475
+ A permutation, alternatively known as an 'arrangement number' or 'ordering'
476
+ is an arrangement of the elements of an ordered list into a one-to-one
477
+ mapping with itself. The permutation of a given arrangement is given by
478
+ indicating the positions of the elements after re-arrangement [2]_. For
479
+ example, if one started with elements ``[x, y, a, b]`` (in that order) and
480
+ they were reordered as ``[x, y, b, a]`` then the permutation would be
481
+ ``[0, 1, 3, 2]``. Notice that (in SymPy) the first element is always referred
482
+ to as 0 and the permutation uses the indices of the elements in the
483
+ original ordering, not the elements ``(a, b, ...)`` themselves.
484
+
485
+ >>> from sympy.combinatorics import Permutation
486
+ >>> from sympy import init_printing
487
+ >>> init_printing(perm_cyclic=False, pretty_print=False)
488
+
489
+ Permutations Notation
490
+ =====================
491
+
492
+ Permutations are commonly represented in disjoint cycle or array forms.
493
+
494
+ Array Notation and 2-line Form
495
+ ------------------------------------
496
+
497
+ In the 2-line form, the elements and their final positions are shown
498
+ as a matrix with 2 rows:
499
+
500
+ [0 1 2 ... n-1]
501
+ [p(0) p(1) p(2) ... p(n-1)]
502
+
503
+ Since the first line is always ``range(n)``, where n is the size of p,
504
+ it is sufficient to represent the permutation by the second line,
505
+ referred to as the "array form" of the permutation. This is entered
506
+ in brackets as the argument to the Permutation class:
507
+
508
+ >>> p = Permutation([0, 2, 1]); p
509
+ Permutation([0, 2, 1])
510
+
511
+ Given i in range(p.size), the permutation maps i to i^p
512
+
513
+ >>> [i^p for i in range(p.size)]
514
+ [0, 2, 1]
515
+
516
+ The composite of two permutations p*q means first apply p, then q, so
517
+ i^(p*q) = (i^p)^q which is i^p^q according to Python precedence rules:
518
+
519
+ >>> q = Permutation([2, 1, 0])
520
+ >>> [i^p^q for i in range(3)]
521
+ [2, 0, 1]
522
+ >>> [i^(p*q) for i in range(3)]
523
+ [2, 0, 1]
524
+
525
+ One can use also the notation p(i) = i^p, but then the composition
526
+ rule is (p*q)(i) = q(p(i)), not p(q(i)):
527
+
528
+ >>> [(p*q)(i) for i in range(p.size)]
529
+ [2, 0, 1]
530
+ >>> [q(p(i)) for i in range(p.size)]
531
+ [2, 0, 1]
532
+ >>> [p(q(i)) for i in range(p.size)]
533
+ [1, 2, 0]
534
+
535
+ Disjoint Cycle Notation
536
+ -----------------------
537
+
538
+ In disjoint cycle notation, only the elements that have shifted are
539
+ indicated.
540
+
541
+ For example, [1, 3, 2, 0] can be represented as (0, 1, 3)(2).
542
+ This can be understood from the 2 line format of the given permutation.
543
+ In the 2-line form,
544
+ [0 1 2 3]
545
+ [1 3 2 0]
546
+
547
+ The element in the 0th position is 1, so 0 -> 1. The element in the 1st
548
+ position is three, so 1 -> 3. And the element in the third position is again
549
+ 0, so 3 -> 0. Thus, 0 -> 1 -> 3 -> 0, and 2 -> 2. Thus, this can be represented
550
+ as 2 cycles: (0, 1, 3)(2).
551
+ In common notation, singular cycles are not explicitly written as they can be
552
+ inferred implicitly.
553
+
554
+ Only the relative ordering of elements in a cycle matter:
555
+
556
+ >>> Permutation(1,2,3) == Permutation(2,3,1) == Permutation(3,1,2)
557
+ True
558
+
559
+ The disjoint cycle notation is convenient when representing
560
+ permutations that have several cycles in them:
561
+
562
+ >>> Permutation(1, 2)(3, 5) == Permutation([[1, 2], [3, 5]])
563
+ True
564
+
565
+ It also provides some economy in entry when computing products of
566
+ permutations that are written in disjoint cycle notation:
567
+
568
+ >>> Permutation(1, 2)(1, 3)(2, 3)
569
+ Permutation([0, 3, 2, 1])
570
+ >>> _ == Permutation([[1, 2]])*Permutation([[1, 3]])*Permutation([[2, 3]])
571
+ True
572
+
573
+ Caution: when the cycles have common elements between them then the order
574
+ in which the permutations are applied matters. This module applies
575
+ the permutations from *left to right*.
576
+
577
+ >>> Permutation(1, 2)(2, 3) == Permutation([(1, 2), (2, 3)])
578
+ True
579
+ >>> Permutation(1, 2)(2, 3).list()
580
+ [0, 3, 1, 2]
581
+
582
+ In the above case, (1,2) is computed before (2,3).
583
+ As 0 -> 0, 0 -> 0, element in position 0 is 0.
584
+ As 1 -> 2, 2 -> 3, element in position 1 is 3.
585
+ As 2 -> 1, 1 -> 1, element in position 2 is 1.
586
+ As 3 -> 3, 3 -> 2, element in position 3 is 2.
587
+
588
+ If the first and second elements had been
589
+ swapped first, followed by the swapping of the second
590
+ and third, the result would have been [0, 2, 3, 1].
591
+ If, you want to apply the cycles in the conventional
592
+ right to left order, call the function with arguments in reverse order
593
+ as demonstrated below:
594
+
595
+ >>> Permutation([(1, 2), (2, 3)][::-1]).list()
596
+ [0, 2, 3, 1]
597
+
598
+ Entering a singleton in a permutation is a way to indicate the size of the
599
+ permutation. The ``size`` keyword can also be used.
600
+
601
+ Array-form entry:
602
+
603
+ >>> Permutation([[1, 2], [9]])
604
+ Permutation([0, 2, 1], size=10)
605
+ >>> Permutation([[1, 2]], size=10)
606
+ Permutation([0, 2, 1], size=10)
607
+
608
+ Cyclic-form entry:
609
+
610
+ >>> Permutation(1, 2, size=10)
611
+ Permutation([0, 2, 1], size=10)
612
+ >>> Permutation(9)(1, 2)
613
+ Permutation([0, 2, 1], size=10)
614
+
615
+ Caution: no singleton containing an element larger than the largest
616
+ in any previous cycle can be entered. This is an important difference
617
+ in how Permutation and Cycle handle the ``__call__`` syntax. A singleton
618
+ argument at the start of a Permutation performs instantiation of the
619
+ Permutation and is permitted:
620
+
621
+ >>> Permutation(5)
622
+ Permutation([], size=6)
623
+
624
+ A singleton entered after instantiation is a call to the permutation
625
+ -- a function call -- and if the argument is out of range it will
626
+ trigger an error. For this reason, it is better to start the cycle
627
+ with the singleton:
628
+
629
+ The following fails because there is no element 3:
630
+
631
+ >>> Permutation(1, 2)(3)
632
+ Traceback (most recent call last):
633
+ ...
634
+ IndexError: list index out of range
635
+
636
+ This is ok: only the call to an out of range singleton is prohibited;
637
+ otherwise the permutation autosizes:
638
+
639
+ >>> Permutation(3)(1, 2)
640
+ Permutation([0, 2, 1, 3])
641
+ >>> Permutation(1, 2)(3, 4) == Permutation(3, 4)(1, 2)
642
+ True
643
+
644
+
645
+ Equality testing
646
+ ----------------
647
+
648
+ The array forms must be the same in order for permutations to be equal:
649
+
650
+ >>> Permutation([1, 0, 2, 3]) == Permutation([1, 0])
651
+ False
652
+
653
+
654
+ Identity Permutation
655
+ --------------------
656
+
657
+ The identity permutation is a permutation in which no element is out of
658
+ place. It can be entered in a variety of ways. All the following create
659
+ an identity permutation of size 4:
660
+
661
+ >>> I = Permutation([0, 1, 2, 3])
662
+ >>> all(p == I for p in [
663
+ ... Permutation(3),
664
+ ... Permutation(range(4)),
665
+ ... Permutation([], size=4),
666
+ ... Permutation(size=4)])
667
+ True
668
+
669
+ Watch out for entering the range *inside* a set of brackets (which is
670
+ cycle notation):
671
+
672
+ >>> I == Permutation([range(4)])
673
+ False
674
+
675
+
676
+ Permutation Printing
677
+ ====================
678
+
679
+ There are a few things to note about how Permutations are printed.
680
+
681
+ .. deprecated:: 1.6
682
+
683
+ Configuring Permutation printing by setting
684
+ ``Permutation.print_cyclic`` is deprecated. Users should use the
685
+ ``perm_cyclic`` flag to the printers, as described below.
686
+
687
+ 1) If you prefer one form (array or cycle) over another, you can set
688
+ ``init_printing`` with the ``perm_cyclic`` flag.
689
+
690
+ >>> from sympy import init_printing
691
+ >>> p = Permutation(1, 2)(4, 5)(3, 4)
692
+ >>> p
693
+ Permutation([0, 2, 1, 4, 5, 3])
694
+
695
+ >>> init_printing(perm_cyclic=True, pretty_print=False)
696
+ >>> p
697
+ (1 2)(3 4 5)
698
+
699
+ 2) Regardless of the setting, a list of elements in the array for cyclic
700
+ form can be obtained and either of those can be copied and supplied as
701
+ the argument to Permutation:
702
+
703
+ >>> p.array_form
704
+ [0, 2, 1, 4, 5, 3]
705
+ >>> p.cyclic_form
706
+ [[1, 2], [3, 4, 5]]
707
+ >>> Permutation(_) == p
708
+ True
709
+
710
+ 3) Printing is economical in that as little as possible is printed while
711
+ retaining all information about the size of the permutation:
712
+
713
+ >>> init_printing(perm_cyclic=False, pretty_print=False)
714
+ >>> Permutation([1, 0, 2, 3])
715
+ Permutation([1, 0, 2, 3])
716
+ >>> Permutation([1, 0, 2, 3], size=20)
717
+ Permutation([1, 0], size=20)
718
+ >>> Permutation([1, 0, 2, 4, 3, 5, 6], size=20)
719
+ Permutation([1, 0, 2, 4, 3], size=20)
720
+
721
+ >>> p = Permutation([1, 0, 2, 3])
722
+ >>> init_printing(perm_cyclic=True, pretty_print=False)
723
+ >>> p
724
+ (3)(0 1)
725
+ >>> init_printing(perm_cyclic=False, pretty_print=False)
726
+
727
+ The 2 was not printed but it is still there as can be seen with the
728
+ array_form and size methods:
729
+
730
+ >>> p.array_form
731
+ [1, 0, 2, 3]
732
+ >>> p.size
733
+ 4
734
+
735
+ Short introduction to other methods
736
+ ===================================
737
+
738
+ The permutation can act as a bijective function, telling what element is
739
+ located at a given position
740
+
741
+ >>> q = Permutation([5, 2, 3, 4, 1, 0])
742
+ >>> q.array_form[1] # the hard way
743
+ 2
744
+ >>> q(1) # the easy way
745
+ 2
746
+ >>> {i: q(i) for i in range(q.size)} # showing the bijection
747
+ {0: 5, 1: 2, 2: 3, 3: 4, 4: 1, 5: 0}
748
+
749
+ The full cyclic form (including singletons) can be obtained:
750
+
751
+ >>> p.full_cyclic_form
752
+ [[0, 1], [2], [3]]
753
+
754
+ Any permutation can be factored into transpositions of pairs of elements:
755
+
756
+ >>> Permutation([[1, 2], [3, 4, 5]]).transpositions()
757
+ [(1, 2), (3, 5), (3, 4)]
758
+ >>> Permutation.rmul(*[Permutation([ti], size=6) for ti in _]).cyclic_form
759
+ [[1, 2], [3, 4, 5]]
760
+
761
+ The number of permutations on a set of n elements is given by n! and is
762
+ called the cardinality.
763
+
764
+ >>> p.size
765
+ 4
766
+ >>> p.cardinality
767
+ 24
768
+
769
+ A given permutation has a rank among all the possible permutations of the
770
+ same elements, but what that rank is depends on how the permutations are
771
+ enumerated. (There are a number of different methods of doing so.) The
772
+ lexicographic rank is given by the rank method and this rank is used to
773
+ increment a permutation with addition/subtraction:
774
+
775
+ >>> p.rank()
776
+ 6
777
+ >>> p + 1
778
+ Permutation([1, 0, 3, 2])
779
+ >>> p.next_lex()
780
+ Permutation([1, 0, 3, 2])
781
+ >>> _.rank()
782
+ 7
783
+ >>> p.unrank_lex(p.size, rank=7)
784
+ Permutation([1, 0, 3, 2])
785
+
786
+ The product of two permutations p and q is defined as their composition as
787
+ functions, (p*q)(i) = q(p(i)) [6]_.
788
+
789
+ >>> p = Permutation([1, 0, 2, 3])
790
+ >>> q = Permutation([2, 3, 1, 0])
791
+ >>> list(q*p)
792
+ [2, 3, 0, 1]
793
+ >>> list(p*q)
794
+ [3, 2, 1, 0]
795
+ >>> [q(p(i)) for i in range(p.size)]
796
+ [3, 2, 1, 0]
797
+
798
+ The permutation can be 'applied' to any list-like object, not only
799
+ Permutations:
800
+
801
+ >>> p(['zero', 'one', 'four', 'two'])
802
+ ['one', 'zero', 'four', 'two']
803
+ >>> p('zo42')
804
+ ['o', 'z', '4', '2']
805
+
806
+ If you have a list of arbitrary elements, the corresponding permutation
807
+ can be found with the from_sequence method:
808
+
809
+ >>> Permutation.from_sequence('SymPy')
810
+ Permutation([1, 3, 2, 0, 4])
811
+
812
+ Checking if a Permutation is contained in a Group
813
+ =================================================
814
+
815
+ Generally if you have a group of permutations G on n symbols, and
816
+ you're checking if a permutation on less than n symbols is part
817
+ of that group, the check will fail.
818
+
819
+ Here is an example for n=5 and we check if the cycle
820
+ (1,2,3) is in G:
821
+
822
+ >>> from sympy import init_printing
823
+ >>> init_printing(perm_cyclic=True, pretty_print=False)
824
+ >>> from sympy.combinatorics import Cycle, Permutation
825
+ >>> from sympy.combinatorics.perm_groups import PermutationGroup
826
+ >>> G = PermutationGroup(Cycle(2, 3)(4, 5), Cycle(1, 2, 3, 4, 5))
827
+ >>> p1 = Permutation(Cycle(2, 5, 3))
828
+ >>> p2 = Permutation(Cycle(1, 2, 3))
829
+ >>> a1 = Permutation(Cycle(1, 2, 3).list(6))
830
+ >>> a2 = Permutation(Cycle(1, 2, 3)(5))
831
+ >>> a3 = Permutation(Cycle(1, 2, 3),size=6)
832
+ >>> for p in [p1,p2,a1,a2,a3]: p, G.contains(p)
833
+ ((2 5 3), True)
834
+ ((1 2 3), False)
835
+ ((5)(1 2 3), True)
836
+ ((5)(1 2 3), True)
837
+ ((5)(1 2 3), True)
838
+
839
+ The check for p2 above will fail.
840
+
841
+ Checking if p1 is in G works because SymPy knows
842
+ G is a group on 5 symbols, and p1 is also on 5 symbols
843
+ (its largest element is 5).
844
+
845
+ For ``a1``, the ``.list(6)`` call will extend the permutation to 5
846
+ symbols, so the test will work as well. In the case of ``a2`` the
847
+ permutation is being extended to 5 symbols by using a singleton,
848
+ and in the case of ``a3`` it's extended through the constructor
849
+ argument ``size=6``.
850
+
851
+ There is another way to do this, which is to tell the ``contains``
852
+ method that the number of symbols the group is on does not need to
853
+ match perfectly the number of symbols for the permutation:
854
+
855
+ >>> G.contains(p2,strict=False)
856
+ True
857
+
858
+ This can be via the ``strict`` argument to the ``contains`` method,
859
+ and SymPy will try to extend the permutation on its own and then
860
+ perform the containment check.
861
+
862
+ See Also
863
+ ========
864
+
865
+ Cycle
866
+
867
+ References
868
+ ==========
869
+
870
+ .. [1] Skiena, S. 'Permutations.' 1.1 in Implementing Discrete Mathematics
871
+ Combinatorics and Graph Theory with Mathematica. Reading, MA:
872
+ Addison-Wesley, pp. 3-16, 1990.
873
+
874
+ .. [2] Knuth, D. E. The Art of Computer Programming, Vol. 4: Combinatorial
875
+ Algorithms, 1st ed. Reading, MA: Addison-Wesley, 2011.
876
+
877
+ .. [3] Wendy Myrvold and Frank Ruskey. 2001. Ranking and unranking
878
+ permutations in linear time. Inf. Process. Lett. 79, 6 (September 2001),
879
+ 281-284. DOI=10.1016/S0020-0190(01)00141-7
880
+
881
+ .. [4] D. L. Kreher, D. R. Stinson 'Combinatorial Algorithms'
882
+ CRC Press, 1999
883
+
884
+ .. [5] Graham, R. L.; Knuth, D. E.; and Patashnik, O.
885
+ Concrete Mathematics: A Foundation for Computer Science, 2nd ed.
886
+ Reading, MA: Addison-Wesley, 1994.
887
+
888
+ .. [6] https://en.wikipedia.org/w/index.php?oldid=499948155#Product_and_inverse
889
+
890
+ .. [7] https://en.wikipedia.org/wiki/Lehmer_code
891
+
892
+ """
893
+
894
+ is_Permutation = True
895
+
896
+ _array_form = None
897
+ _cyclic_form = None
898
+ _cycle_structure = None
899
+ _size = None
900
+ _rank = None
901
+
902
+ def __new__(cls, *args, size=None, **kwargs):
903
+ """
904
+ Constructor for the Permutation object from a list or a
905
+ list of lists in which all elements of the permutation may
906
+ appear only once.
907
+
908
+ Examples
909
+ ========
910
+
911
+ >>> from sympy.combinatorics import Permutation
912
+ >>> from sympy import init_printing
913
+ >>> init_printing(perm_cyclic=False, pretty_print=False)
914
+
915
+ Permutations entered in array-form are left unaltered:
916
+
917
+ >>> Permutation([0, 2, 1])
918
+ Permutation([0, 2, 1])
919
+
920
+ Permutations entered in cyclic form are converted to array form;
921
+ singletons need not be entered, but can be entered to indicate the
922
+ largest element:
923
+
924
+ >>> Permutation([[4, 5, 6], [0, 1]])
925
+ Permutation([1, 0, 2, 3, 5, 6, 4])
926
+ >>> Permutation([[4, 5, 6], [0, 1], [19]])
927
+ Permutation([1, 0, 2, 3, 5, 6, 4], size=20)
928
+
929
+ All manipulation of permutations assumes that the smallest element
930
+ is 0 (in keeping with 0-based indexing in Python) so if the 0 is
931
+ missing when entering a permutation in array form, an error will be
932
+ raised:
933
+
934
+ >>> Permutation([2, 1])
935
+ Traceback (most recent call last):
936
+ ...
937
+ ValueError: Integers 0 through 2 must be present.
938
+
939
+ If a permutation is entered in cyclic form, it can be entered without
940
+ singletons and the ``size`` specified so those values can be filled
941
+ in, otherwise the array form will only extend to the maximum value
942
+ in the cycles:
943
+
944
+ >>> Permutation([[1, 4], [3, 5, 2]], size=10)
945
+ Permutation([0, 4, 3, 5, 1, 2], size=10)
946
+ >>> _.array_form
947
+ [0, 4, 3, 5, 1, 2, 6, 7, 8, 9]
948
+ """
949
+ if size is not None:
950
+ size = int(size)
951
+
952
+ #a) ()
953
+ #b) (1) = identity
954
+ #c) (1, 2) = cycle
955
+ #d) ([1, 2, 3]) = array form
956
+ #e) ([[1, 2]]) = cyclic form
957
+ #f) (Cycle) = conversion to permutation
958
+ #g) (Permutation) = adjust size or return copy
959
+ ok = True
960
+ if not args: # a
961
+ return cls._af_new(list(range(size or 0)))
962
+ elif len(args) > 1: # c
963
+ return cls._af_new(Cycle(*args).list(size))
964
+ if len(args) == 1:
965
+ a = args[0]
966
+ if isinstance(a, cls): # g
967
+ if size is None or size == a.size:
968
+ return a
969
+ return cls(a.array_form, size=size)
970
+ if isinstance(a, Cycle): # f
971
+ return cls._af_new(a.list(size))
972
+ if not is_sequence(a): # b
973
+ if size is not None and a + 1 > size:
974
+ raise ValueError('size is too small when max is %s' % a)
975
+ return cls._af_new(list(range(a + 1)))
976
+ if has_variety(is_sequence(ai) for ai in a):
977
+ ok = False
978
+ else:
979
+ ok = False
980
+ if not ok:
981
+ raise ValueError("Permutation argument must be a list of ints, "
982
+ "a list of lists, Permutation or Cycle.")
983
+
984
+ # safe to assume args are valid; this also makes a copy
985
+ # of the args
986
+ args = list(args[0])
987
+
988
+ is_cycle = args and is_sequence(args[0])
989
+ if is_cycle: # e
990
+ args = [[int(i) for i in c] for c in args]
991
+ else: # d
992
+ args = [int(i) for i in args]
993
+
994
+ # if there are n elements present, 0, 1, ..., n-1 should be present
995
+ # unless a cycle notation has been provided. A 0 will be added
996
+ # for convenience in case one wants to enter permutations where
997
+ # counting starts from 1.
998
+
999
+ temp = flatten(args)
1000
+ if has_dups(temp) and not is_cycle:
1001
+ raise ValueError('there were repeated elements.')
1002
+ temp = set(temp)
1003
+
1004
+ if not is_cycle:
1005
+ if temp != set(range(len(temp))):
1006
+ raise ValueError('Integers 0 through %s must be present.' %
1007
+ max(temp))
1008
+ if size is not None and temp and max(temp) + 1 > size:
1009
+ raise ValueError('max element should not exceed %s' % (size - 1))
1010
+
1011
+ if is_cycle:
1012
+ # it's not necessarily canonical so we won't store
1013
+ # it -- use the array form instead
1014
+ c = Cycle()
1015
+ for ci in args:
1016
+ c = c(*ci)
1017
+ aform = c.list()
1018
+ else:
1019
+ aform = list(args)
1020
+ if size and size > len(aform):
1021
+ # don't allow for truncation of permutation which
1022
+ # might split a cycle and lead to an invalid aform
1023
+ # but do allow the permutation size to be increased
1024
+ aform.extend(list(range(len(aform), size)))
1025
+
1026
+ return cls._af_new(aform)
1027
+
1028
+ @classmethod
1029
+ def _af_new(cls, perm):
1030
+ """A method to produce a Permutation object from a list;
1031
+ the list is bound to the _array_form attribute, so it must
1032
+ not be modified; this method is meant for internal use only;
1033
+ the list ``a`` is supposed to be generated as a temporary value
1034
+ in a method, so p = Perm._af_new(a) is the only object
1035
+ to hold a reference to ``a``::
1036
+
1037
+ Examples
1038
+ ========
1039
+
1040
+ >>> from sympy.combinatorics.permutations import Perm
1041
+ >>> from sympy import init_printing
1042
+ >>> init_printing(perm_cyclic=False, pretty_print=False)
1043
+ >>> a = [2, 1, 3, 0]
1044
+ >>> p = Perm._af_new(a)
1045
+ >>> p
1046
+ Permutation([2, 1, 3, 0])
1047
+
1048
+ """
1049
+ p = super().__new__(cls)
1050
+ p._array_form = perm
1051
+ p._size = len(perm)
1052
+ return p
1053
+
1054
+ def copy(self):
1055
+ return self.__class__(self.array_form)
1056
+
1057
+ def __getnewargs__(self):
1058
+ return (self.array_form,)
1059
+
1060
+ def _hashable_content(self):
1061
+ # the array_form (a list) is the Permutation arg, so we need to
1062
+ # return a tuple, instead
1063
+ return tuple(self.array_form)
1064
+
1065
+ @property
1066
+ def array_form(self):
1067
+ """
1068
+ Return a copy of the attribute _array_form
1069
+ Examples
1070
+ ========
1071
+
1072
+ >>> from sympy.combinatorics import Permutation
1073
+ >>> p = Permutation([[2, 0], [3, 1]])
1074
+ >>> p.array_form
1075
+ [2, 3, 0, 1]
1076
+ >>> Permutation([[2, 0, 3, 1]]).array_form
1077
+ [3, 2, 0, 1]
1078
+ >>> Permutation([2, 0, 3, 1]).array_form
1079
+ [2, 0, 3, 1]
1080
+ >>> Permutation([[1, 2], [4, 5]]).array_form
1081
+ [0, 2, 1, 3, 5, 4]
1082
+ """
1083
+ return self._array_form[:]
1084
+
1085
+ def list(self, size=None):
1086
+ """Return the permutation as an explicit list, possibly
1087
+ trimming unmoved elements if size is less than the maximum
1088
+ element in the permutation; if this is desired, setting
1089
+ ``size=-1`` will guarantee such trimming.
1090
+
1091
+ Examples
1092
+ ========
1093
+
1094
+ >>> from sympy.combinatorics import Permutation
1095
+ >>> p = Permutation(2, 3)(4, 5)
1096
+ >>> p.list()
1097
+ [0, 1, 3, 2, 5, 4]
1098
+ >>> p.list(10)
1099
+ [0, 1, 3, 2, 5, 4, 6, 7, 8, 9]
1100
+
1101
+ Passing a length too small will trim trailing, unchanged elements
1102
+ in the permutation:
1103
+
1104
+ >>> Permutation(2, 4)(1, 2, 4).list(-1)
1105
+ [0, 2, 1]
1106
+ >>> Permutation(3).list(-1)
1107
+ []
1108
+ """
1109
+ if not self and size is None:
1110
+ raise ValueError('must give size for empty Cycle')
1111
+ rv = self.array_form
1112
+ if size is not None:
1113
+ if size > self.size:
1114
+ rv.extend(list(range(self.size, size)))
1115
+ else:
1116
+ # find first value from rhs where rv[i] != i
1117
+ i = self.size - 1
1118
+ while rv:
1119
+ if rv[-1] != i:
1120
+ break
1121
+ rv.pop()
1122
+ i -= 1
1123
+ return rv
1124
+
1125
+ @property
1126
+ def cyclic_form(self):
1127
+ """
1128
+ This is used to convert to the cyclic notation
1129
+ from the canonical notation. Singletons are omitted.
1130
+
1131
+ Examples
1132
+ ========
1133
+
1134
+ >>> from sympy.combinatorics import Permutation
1135
+ >>> p = Permutation([0, 3, 1, 2])
1136
+ >>> p.cyclic_form
1137
+ [[1, 3, 2]]
1138
+ >>> Permutation([1, 0, 2, 4, 3, 5]).cyclic_form
1139
+ [[0, 1], [3, 4]]
1140
+
1141
+ See Also
1142
+ ========
1143
+
1144
+ array_form, full_cyclic_form
1145
+ """
1146
+ if self._cyclic_form is not None:
1147
+ return list(self._cyclic_form)
1148
+ array_form = self.array_form
1149
+ unchecked = [True] * len(array_form)
1150
+ cyclic_form = []
1151
+ for i in range(len(array_form)):
1152
+ if unchecked[i]:
1153
+ cycle = []
1154
+ cycle.append(i)
1155
+ unchecked[i] = False
1156
+ j = i
1157
+ while unchecked[array_form[j]]:
1158
+ j = array_form[j]
1159
+ cycle.append(j)
1160
+ unchecked[j] = False
1161
+ if len(cycle) > 1:
1162
+ cyclic_form.append(cycle)
1163
+ assert cycle == list(minlex(cycle))
1164
+ cyclic_form.sort()
1165
+ self._cyclic_form = cyclic_form.copy()
1166
+ return cyclic_form
1167
+
1168
+ @property
1169
+ def full_cyclic_form(self):
1170
+ """Return permutation in cyclic form including singletons.
1171
+
1172
+ Examples
1173
+ ========
1174
+
1175
+ >>> from sympy.combinatorics import Permutation
1176
+ >>> Permutation([0, 2, 1]).full_cyclic_form
1177
+ [[0], [1, 2]]
1178
+ """
1179
+ need = set(range(self.size)) - set(flatten(self.cyclic_form))
1180
+ rv = self.cyclic_form + [[i] for i in need]
1181
+ rv.sort()
1182
+ return rv
1183
+
1184
+ @property
1185
+ def size(self):
1186
+ """
1187
+ Returns the number of elements in the permutation.
1188
+
1189
+ Examples
1190
+ ========
1191
+
1192
+ >>> from sympy.combinatorics import Permutation
1193
+ >>> Permutation([[3, 2], [0, 1]]).size
1194
+ 4
1195
+
1196
+ See Also
1197
+ ========
1198
+
1199
+ cardinality, length, order, rank
1200
+ """
1201
+ return self._size
1202
+
1203
+ def support(self):
1204
+ """Return the elements in permutation, P, for which P[i] != i.
1205
+
1206
+ Examples
1207
+ ========
1208
+
1209
+ >>> from sympy.combinatorics import Permutation
1210
+ >>> p = Permutation([[3, 2], [0, 1], [4]])
1211
+ >>> p.array_form
1212
+ [1, 0, 3, 2, 4]
1213
+ >>> p.support()
1214
+ [0, 1, 2, 3]
1215
+ """
1216
+ a = self.array_form
1217
+ return [i for i, e in enumerate(a) if e != i]
1218
+
1219
+ def __add__(self, other):
1220
+ """Return permutation that is other higher in rank than self.
1221
+
1222
+ The rank is the lexicographical rank, with the identity permutation
1223
+ having rank of 0.
1224
+
1225
+ Examples
1226
+ ========
1227
+
1228
+ >>> from sympy.combinatorics import Permutation
1229
+ >>> I = Permutation([0, 1, 2, 3])
1230
+ >>> a = Permutation([2, 1, 3, 0])
1231
+ >>> I + a.rank() == a
1232
+ True
1233
+
1234
+ See Also
1235
+ ========
1236
+
1237
+ __sub__, inversion_vector
1238
+
1239
+ """
1240
+ rank = (self.rank() + other) % self.cardinality
1241
+ rv = self.unrank_lex(self.size, rank)
1242
+ rv._rank = rank
1243
+ return rv
1244
+
1245
+ def __sub__(self, other):
1246
+ """Return the permutation that is other lower in rank than self.
1247
+
1248
+ See Also
1249
+ ========
1250
+
1251
+ __add__
1252
+ """
1253
+ return self.__add__(-other)
1254
+
1255
+ @staticmethod
1256
+ def rmul(*args):
1257
+ """
1258
+ Return product of Permutations [a, b, c, ...] as the Permutation whose
1259
+ ith value is a(b(c(i))).
1260
+
1261
+ a, b, c, ... can be Permutation objects or tuples.
1262
+
1263
+ Examples
1264
+ ========
1265
+
1266
+ >>> from sympy.combinatorics import Permutation
1267
+
1268
+ >>> a, b = [1, 0, 2], [0, 2, 1]
1269
+ >>> a = Permutation(a); b = Permutation(b)
1270
+ >>> list(Permutation.rmul(a, b))
1271
+ [1, 2, 0]
1272
+ >>> [a(b(i)) for i in range(3)]
1273
+ [1, 2, 0]
1274
+
1275
+ This handles the operands in reverse order compared to the ``*`` operator:
1276
+
1277
+ >>> a = Permutation(a); b = Permutation(b)
1278
+ >>> list(a*b)
1279
+ [2, 0, 1]
1280
+ >>> [b(a(i)) for i in range(3)]
1281
+ [2, 0, 1]
1282
+
1283
+ Notes
1284
+ =====
1285
+
1286
+ All items in the sequence will be parsed by Permutation as
1287
+ necessary as long as the first item is a Permutation:
1288
+
1289
+ >>> Permutation.rmul(a, [0, 2, 1]) == Permutation.rmul(a, b)
1290
+ True
1291
+
1292
+ The reverse order of arguments will raise a TypeError.
1293
+
1294
+ """
1295
+ rv = args[0]
1296
+ for i in range(1, len(args)):
1297
+ rv = args[i]*rv
1298
+ return rv
1299
+
1300
+ @classmethod
1301
+ def rmul_with_af(cls, *args):
1302
+ """
1303
+ same as rmul, but the elements of args are Permutation objects
1304
+ which have _array_form
1305
+ """
1306
+ a = [x._array_form for x in args]
1307
+ rv = cls._af_new(_af_rmuln(*a))
1308
+ return rv
1309
+
1310
+ def mul_inv(self, other):
1311
+ """
1312
+ other*~self, self and other have _array_form
1313
+ """
1314
+ a = _af_invert(self._array_form)
1315
+ b = other._array_form
1316
+ return self._af_new(_af_rmul(a, b))
1317
+
1318
+ def __rmul__(self, other):
1319
+ """This is needed to coerce other to Permutation in rmul."""
1320
+ cls = type(self)
1321
+ return cls(other)*self
1322
+
1323
+ def __mul__(self, other):
1324
+ """
1325
+ Return the product a*b as a Permutation; the ith value is b(a(i)).
1326
+
1327
+ Examples
1328
+ ========
1329
+
1330
+ >>> from sympy.combinatorics.permutations import _af_rmul, Permutation
1331
+
1332
+ >>> a, b = [1, 0, 2], [0, 2, 1]
1333
+ >>> a = Permutation(a); b = Permutation(b)
1334
+ >>> list(a*b)
1335
+ [2, 0, 1]
1336
+ >>> [b(a(i)) for i in range(3)]
1337
+ [2, 0, 1]
1338
+
1339
+ This handles operands in reverse order compared to _af_rmul and rmul:
1340
+
1341
+ >>> al = list(a); bl = list(b)
1342
+ >>> _af_rmul(al, bl)
1343
+ [1, 2, 0]
1344
+ >>> [al[bl[i]] for i in range(3)]
1345
+ [1, 2, 0]
1346
+
1347
+ It is acceptable for the arrays to have different lengths; the shorter
1348
+ one will be padded to match the longer one:
1349
+
1350
+ >>> from sympy import init_printing
1351
+ >>> init_printing(perm_cyclic=False, pretty_print=False)
1352
+ >>> b*Permutation([1, 0])
1353
+ Permutation([1, 2, 0])
1354
+ >>> Permutation([1, 0])*b
1355
+ Permutation([2, 0, 1])
1356
+
1357
+ It is also acceptable to allow coercion to handle conversion of a
1358
+ single list to the left of a Permutation:
1359
+
1360
+ >>> [0, 1]*a # no change: 2-element identity
1361
+ Permutation([1, 0, 2])
1362
+ >>> [[0, 1]]*a # exchange first two elements
1363
+ Permutation([0, 1, 2])
1364
+
1365
+ You cannot use more than 1 cycle notation in a product of cycles
1366
+ since coercion can only handle one argument to the left. To handle
1367
+ multiple cycles it is convenient to use Cycle instead of Permutation:
1368
+
1369
+ >>> [[1, 2]]*[[2, 3]]*Permutation([]) # doctest: +SKIP
1370
+ >>> from sympy.combinatorics.permutations import Cycle
1371
+ >>> Cycle(1, 2)(2, 3)
1372
+ (1 3 2)
1373
+
1374
+ """
1375
+ from sympy.combinatorics.perm_groups import PermutationGroup, Coset
1376
+ if isinstance(other, PermutationGroup):
1377
+ return Coset(self, other, dir='-')
1378
+ a = self.array_form
1379
+ # __rmul__ makes sure the other is a Permutation
1380
+ b = other.array_form
1381
+ if not b:
1382
+ perm = a
1383
+ else:
1384
+ b.extend(list(range(len(b), len(a))))
1385
+ perm = [b[i] for i in a] + b[len(a):]
1386
+ return self._af_new(perm)
1387
+
1388
+ def commutes_with(self, other):
1389
+ """
1390
+ Checks if the elements are commuting.
1391
+
1392
+ Examples
1393
+ ========
1394
+
1395
+ >>> from sympy.combinatorics import Permutation
1396
+ >>> a = Permutation([1, 4, 3, 0, 2, 5])
1397
+ >>> b = Permutation([0, 1, 2, 3, 4, 5])
1398
+ >>> a.commutes_with(b)
1399
+ True
1400
+ >>> b = Permutation([2, 3, 5, 4, 1, 0])
1401
+ >>> a.commutes_with(b)
1402
+ False
1403
+ """
1404
+ a = self.array_form
1405
+ b = other.array_form
1406
+ return _af_commutes_with(a, b)
1407
+
1408
+ def __pow__(self, n):
1409
+ """
1410
+ Routine for finding powers of a permutation.
1411
+
1412
+ Examples
1413
+ ========
1414
+
1415
+ >>> from sympy.combinatorics import Permutation
1416
+ >>> from sympy import init_printing
1417
+ >>> init_printing(perm_cyclic=False, pretty_print=False)
1418
+ >>> p = Permutation([2, 0, 3, 1])
1419
+ >>> p.order()
1420
+ 4
1421
+ >>> p**4
1422
+ Permutation([0, 1, 2, 3])
1423
+ """
1424
+ if isinstance(n, Permutation):
1425
+ raise NotImplementedError(
1426
+ 'p**p is not defined; do you mean p^p (conjugate)?')
1427
+ n = int(n)
1428
+ return self._af_new(_af_pow(self.array_form, n))
1429
+
1430
+ def __rxor__(self, i):
1431
+ """Return self(i) when ``i`` is an int.
1432
+
1433
+ Examples
1434
+ ========
1435
+
1436
+ >>> from sympy.combinatorics import Permutation
1437
+ >>> p = Permutation(1, 2, 9)
1438
+ >>> 2^p == p(2) == 9
1439
+ True
1440
+ """
1441
+ if int_valued(i):
1442
+ return self(i)
1443
+ else:
1444
+ raise NotImplementedError(
1445
+ "i^p = p(i) when i is an integer, not %s." % i)
1446
+
1447
+ def __xor__(self, h):
1448
+ """Return the conjugate permutation ``~h*self*h` `.
1449
+
1450
+ Explanation
1451
+ ===========
1452
+
1453
+ If ``a`` and ``b`` are conjugates, ``a = h*b*~h`` and
1454
+ ``b = ~h*a*h`` and both have the same cycle structure.
1455
+
1456
+ Examples
1457
+ ========
1458
+
1459
+ >>> from sympy.combinatorics import Permutation
1460
+ >>> p = Permutation(1, 2, 9)
1461
+ >>> q = Permutation(6, 9, 8)
1462
+ >>> p*q != q*p
1463
+ True
1464
+
1465
+ Calculate and check properties of the conjugate:
1466
+
1467
+ >>> c = p^q
1468
+ >>> c == ~q*p*q and p == q*c*~q
1469
+ True
1470
+
1471
+ The expression q^p^r is equivalent to q^(p*r):
1472
+
1473
+ >>> r = Permutation(9)(4, 6, 8)
1474
+ >>> q^p^r == q^(p*r)
1475
+ True
1476
+
1477
+ If the term to the left of the conjugate operator, i, is an integer
1478
+ then this is interpreted as selecting the ith element from the
1479
+ permutation to the right:
1480
+
1481
+ >>> all(i^p == p(i) for i in range(p.size))
1482
+ True
1483
+
1484
+ Note that the * operator as higher precedence than the ^ operator:
1485
+
1486
+ >>> q^r*p^r == q^(r*p)^r == Permutation(9)(1, 6, 4)
1487
+ True
1488
+
1489
+ Notes
1490
+ =====
1491
+
1492
+ In Python the precedence rule is p^q^r = (p^q)^r which differs
1493
+ in general from p^(q^r)
1494
+
1495
+ >>> q^p^r
1496
+ (9)(1 4 8)
1497
+ >>> q^(p^r)
1498
+ (9)(1 8 6)
1499
+
1500
+ For a given r and p, both of the following are conjugates of p:
1501
+ ~r*p*r and r*p*~r. But these are not necessarily the same:
1502
+
1503
+ >>> ~r*p*r == r*p*~r
1504
+ True
1505
+
1506
+ >>> p = Permutation(1, 2, 9)(5, 6)
1507
+ >>> ~r*p*r == r*p*~r
1508
+ False
1509
+
1510
+ The conjugate ~r*p*r was chosen so that ``p^q^r`` would be equivalent
1511
+ to ``p^(q*r)`` rather than ``p^(r*q)``. To obtain r*p*~r, pass ~r to
1512
+ this method:
1513
+
1514
+ >>> p^~r == r*p*~r
1515
+ True
1516
+ """
1517
+
1518
+ if self.size != h.size:
1519
+ raise ValueError("The permutations must be of equal size.")
1520
+ a = [None]*self.size
1521
+ h = h._array_form
1522
+ p = self._array_form
1523
+ for i in range(self.size):
1524
+ a[h[i]] = h[p[i]]
1525
+ return self._af_new(a)
1526
+
1527
+ def transpositions(self):
1528
+ """
1529
+ Return the permutation decomposed into a list of transpositions.
1530
+
1531
+ Explanation
1532
+ ===========
1533
+
1534
+ It is always possible to express a permutation as the product of
1535
+ transpositions, see [1]
1536
+
1537
+ Examples
1538
+ ========
1539
+
1540
+ >>> from sympy.combinatorics import Permutation
1541
+ >>> p = Permutation([[1, 2, 3], [0, 4, 5, 6, 7]])
1542
+ >>> t = p.transpositions()
1543
+ >>> t
1544
+ [(0, 7), (0, 6), (0, 5), (0, 4), (1, 3), (1, 2)]
1545
+ >>> print(''.join(str(c) for c in t))
1546
+ (0, 7)(0, 6)(0, 5)(0, 4)(1, 3)(1, 2)
1547
+ >>> Permutation.rmul(*[Permutation([ti], size=p.size) for ti in t]) == p
1548
+ True
1549
+
1550
+ References
1551
+ ==========
1552
+
1553
+ .. [1] https://en.wikipedia.org/wiki/Transposition_%28mathematics%29#Properties
1554
+
1555
+ """
1556
+ a = self.cyclic_form
1557
+ res = []
1558
+ for x in a:
1559
+ nx = len(x)
1560
+ if nx == 2:
1561
+ res.append(tuple(x))
1562
+ elif nx > 2:
1563
+ first = x[0]
1564
+ res.extend((first, y) for y in x[nx - 1:0:-1])
1565
+ return res
1566
+
1567
+ @classmethod
1568
+ def from_sequence(self, i, key=None):
1569
+ """Return the permutation needed to obtain ``i`` from the sorted
1570
+ elements of ``i``. If custom sorting is desired, a key can be given.
1571
+
1572
+ Examples
1573
+ ========
1574
+
1575
+ >>> from sympy.combinatorics import Permutation
1576
+
1577
+ >>> Permutation.from_sequence('SymPy')
1578
+ (4)(0 1 3)
1579
+ >>> _(sorted("SymPy"))
1580
+ ['S', 'y', 'm', 'P', 'y']
1581
+ >>> Permutation.from_sequence('SymPy', key=lambda x: x.lower())
1582
+ (4)(0 2)(1 3)
1583
+ """
1584
+ ic = list(zip(i, list(range(len(i)))))
1585
+ if key:
1586
+ ic.sort(key=lambda x: key(x[0]))
1587
+ else:
1588
+ ic.sort()
1589
+ return ~Permutation([i[1] for i in ic])
1590
+
1591
+ def __invert__(self):
1592
+ """
1593
+ Return the inverse of the permutation.
1594
+
1595
+ A permutation multiplied by its inverse is the identity permutation.
1596
+
1597
+ Examples
1598
+ ========
1599
+
1600
+ >>> from sympy.combinatorics import Permutation
1601
+ >>> from sympy import init_printing
1602
+ >>> init_printing(perm_cyclic=False, pretty_print=False)
1603
+ >>> p = Permutation([[2, 0], [3, 1]])
1604
+ >>> ~p
1605
+ Permutation([2, 3, 0, 1])
1606
+ >>> _ == p**-1
1607
+ True
1608
+ >>> p*~p == ~p*p == Permutation([0, 1, 2, 3])
1609
+ True
1610
+ """
1611
+ return self._af_new(_af_invert(self._array_form))
1612
+
1613
+ def __iter__(self):
1614
+ """Yield elements from array form.
1615
+
1616
+ Examples
1617
+ ========
1618
+
1619
+ >>> from sympy.combinatorics import Permutation
1620
+ >>> list(Permutation(range(3)))
1621
+ [0, 1, 2]
1622
+ """
1623
+ yield from self.array_form
1624
+
1625
+ def __repr__(self):
1626
+ return srepr(self)
1627
+
1628
+ def __call__(self, *i):
1629
+ """
1630
+ Allows applying a permutation instance as a bijective function.
1631
+
1632
+ Examples
1633
+ ========
1634
+
1635
+ >>> from sympy.combinatorics import Permutation
1636
+ >>> p = Permutation([[2, 0], [3, 1]])
1637
+ >>> p.array_form
1638
+ [2, 3, 0, 1]
1639
+ >>> [p(i) for i in range(4)]
1640
+ [2, 3, 0, 1]
1641
+
1642
+ If an array is given then the permutation selects the items
1643
+ from the array (i.e. the permutation is applied to the array):
1644
+
1645
+ >>> from sympy.abc import x
1646
+ >>> p([x, 1, 0, x**2])
1647
+ [0, x**2, x, 1]
1648
+ """
1649
+ # list indices can be Integer or int; leave this
1650
+ # as it is (don't test or convert it) because this
1651
+ # gets called a lot and should be fast
1652
+ if len(i) == 1:
1653
+ i = i[0]
1654
+ if not isinstance(i, Iterable):
1655
+ i = as_int(i)
1656
+ if i < 0 or i > self.size:
1657
+ raise TypeError(
1658
+ "{} should be an integer between 0 and {}"
1659
+ .format(i, self.size-1))
1660
+ return self._array_form[i]
1661
+ # P([a, b, c])
1662
+ if len(i) != self.size:
1663
+ raise TypeError(
1664
+ "{} should have the length {}.".format(i, self.size))
1665
+ return [i[j] for j in self._array_form]
1666
+ # P(1, 2, 3)
1667
+ return self*Permutation(Cycle(*i), size=self.size)
1668
+
1669
+ def atoms(self):
1670
+ """
1671
+ Returns all the elements of a permutation
1672
+
1673
+ Examples
1674
+ ========
1675
+
1676
+ >>> from sympy.combinatorics import Permutation
1677
+ >>> Permutation([0, 1, 2, 3, 4, 5]).atoms()
1678
+ {0, 1, 2, 3, 4, 5}
1679
+ >>> Permutation([[0, 1], [2, 3], [4, 5]]).atoms()
1680
+ {0, 1, 2, 3, 4, 5}
1681
+ """
1682
+ return set(self.array_form)
1683
+
1684
+ def apply(self, i):
1685
+ r"""Apply the permutation to an expression.
1686
+
1687
+ Parameters
1688
+ ==========
1689
+
1690
+ i : Expr
1691
+ It should be an integer between $0$ and $n-1$ where $n$
1692
+ is the size of the permutation.
1693
+
1694
+ If it is a symbol or a symbolic expression that can
1695
+ have integer values, an ``AppliedPermutation`` object
1696
+ will be returned which can represent an unevaluated
1697
+ function.
1698
+
1699
+ Notes
1700
+ =====
1701
+
1702
+ Any permutation can be defined as a bijective function
1703
+ $\sigma : \{ 0, 1, \dots, n-1 \} \rightarrow \{ 0, 1, \dots, n-1 \}$
1704
+ where $n$ denotes the size of the permutation.
1705
+
1706
+ The definition may even be extended for any set with distinctive
1707
+ elements, such that the permutation can even be applied for
1708
+ real numbers or such, however, it is not implemented for now for
1709
+ computational reasons and the integrity with the group theory
1710
+ module.
1711
+
1712
+ This function is similar to the ``__call__`` magic, however,
1713
+ ``__call__`` magic already has some other applications like
1714
+ permuting an array or attaching new cycles, which would
1715
+ not always be mathematically consistent.
1716
+
1717
+ This also guarantees that the return type is a SymPy integer,
1718
+ which guarantees the safety to use assumptions.
1719
+ """
1720
+ i = _sympify(i)
1721
+ if i.is_integer is False:
1722
+ raise NotImplementedError("{} should be an integer.".format(i))
1723
+
1724
+ n = self.size
1725
+ if (i < 0) == True or (i >= n) == True:
1726
+ raise NotImplementedError(
1727
+ "{} should be an integer between 0 and {}".format(i, n-1))
1728
+
1729
+ if i.is_Integer:
1730
+ return Integer(self._array_form[i])
1731
+ return AppliedPermutation(self, i)
1732
+
1733
+ def next_lex(self):
1734
+ """
1735
+ Returns the next permutation in lexicographical order.
1736
+ If self is the last permutation in lexicographical order
1737
+ it returns None.
1738
+ See [4] section 2.4.
1739
+
1740
+
1741
+ Examples
1742
+ ========
1743
+
1744
+ >>> from sympy.combinatorics import Permutation
1745
+ >>> p = Permutation([2, 3, 1, 0])
1746
+ >>> p = Permutation([2, 3, 1, 0]); p.rank()
1747
+ 17
1748
+ >>> p = p.next_lex(); p.rank()
1749
+ 18
1750
+
1751
+ See Also
1752
+ ========
1753
+
1754
+ rank, unrank_lex
1755
+ """
1756
+ perm = self.array_form[:]
1757
+ n = len(perm)
1758
+ i = n - 2
1759
+ while perm[i + 1] < perm[i]:
1760
+ i -= 1
1761
+ if i == -1:
1762
+ return None
1763
+ else:
1764
+ j = n - 1
1765
+ while perm[j] < perm[i]:
1766
+ j -= 1
1767
+ perm[j], perm[i] = perm[i], perm[j]
1768
+ i += 1
1769
+ j = n - 1
1770
+ while i < j:
1771
+ perm[j], perm[i] = perm[i], perm[j]
1772
+ i += 1
1773
+ j -= 1
1774
+ return self._af_new(perm)
1775
+
1776
+ @classmethod
1777
+ def unrank_nonlex(self, n, r):
1778
+ """
1779
+ This is a linear time unranking algorithm that does not
1780
+ respect lexicographic order [3].
1781
+
1782
+ Examples
1783
+ ========
1784
+
1785
+ >>> from sympy.combinatorics import Permutation
1786
+ >>> from sympy import init_printing
1787
+ >>> init_printing(perm_cyclic=False, pretty_print=False)
1788
+ >>> Permutation.unrank_nonlex(4, 5)
1789
+ Permutation([2, 0, 3, 1])
1790
+ >>> Permutation.unrank_nonlex(4, -1)
1791
+ Permutation([0, 1, 2, 3])
1792
+
1793
+ See Also
1794
+ ========
1795
+
1796
+ next_nonlex, rank_nonlex
1797
+ """
1798
+ def _unrank1(n, r, a):
1799
+ if n > 0:
1800
+ a[n - 1], a[r % n] = a[r % n], a[n - 1]
1801
+ _unrank1(n - 1, r//n, a)
1802
+
1803
+ id_perm = list(range(n))
1804
+ n = int(n)
1805
+ r = r % ifac(n)
1806
+ _unrank1(n, r, id_perm)
1807
+ return self._af_new(id_perm)
1808
+
1809
+ def rank_nonlex(self, inv_perm=None):
1810
+ """
1811
+ This is a linear time ranking algorithm that does not
1812
+ enforce lexicographic order [3].
1813
+
1814
+
1815
+ Examples
1816
+ ========
1817
+
1818
+ >>> from sympy.combinatorics import Permutation
1819
+ >>> p = Permutation([0, 1, 2, 3])
1820
+ >>> p.rank_nonlex()
1821
+ 23
1822
+
1823
+ See Also
1824
+ ========
1825
+
1826
+ next_nonlex, unrank_nonlex
1827
+ """
1828
+ def _rank1(n, perm, inv_perm):
1829
+ if n == 1:
1830
+ return 0
1831
+ s = perm[n - 1]
1832
+ t = inv_perm[n - 1]
1833
+ perm[n - 1], perm[t] = perm[t], s
1834
+ inv_perm[n - 1], inv_perm[s] = inv_perm[s], t
1835
+ return s + n*_rank1(n - 1, perm, inv_perm)
1836
+
1837
+ if inv_perm is None:
1838
+ inv_perm = (~self).array_form
1839
+ if not inv_perm:
1840
+ return 0
1841
+ perm = self.array_form[:]
1842
+ r = _rank1(len(perm), perm, inv_perm)
1843
+ return r
1844
+
1845
+ def next_nonlex(self):
1846
+ """
1847
+ Returns the next permutation in nonlex order [3].
1848
+ If self is the last permutation in this order it returns None.
1849
+
1850
+ Examples
1851
+ ========
1852
+
1853
+ >>> from sympy.combinatorics import Permutation
1854
+ >>> from sympy import init_printing
1855
+ >>> init_printing(perm_cyclic=False, pretty_print=False)
1856
+ >>> p = Permutation([2, 0, 3, 1]); p.rank_nonlex()
1857
+ 5
1858
+ >>> p = p.next_nonlex(); p
1859
+ Permutation([3, 0, 1, 2])
1860
+ >>> p.rank_nonlex()
1861
+ 6
1862
+
1863
+ See Also
1864
+ ========
1865
+
1866
+ rank_nonlex, unrank_nonlex
1867
+ """
1868
+ r = self.rank_nonlex()
1869
+ if r == ifac(self.size) - 1:
1870
+ return None
1871
+ return self.unrank_nonlex(self.size, r + 1)
1872
+
1873
+ def rank(self):
1874
+ """
1875
+ Returns the lexicographic rank of the permutation.
1876
+
1877
+ Examples
1878
+ ========
1879
+
1880
+ >>> from sympy.combinatorics import Permutation
1881
+ >>> p = Permutation([0, 1, 2, 3])
1882
+ >>> p.rank()
1883
+ 0
1884
+ >>> p = Permutation([3, 2, 1, 0])
1885
+ >>> p.rank()
1886
+ 23
1887
+
1888
+ See Also
1889
+ ========
1890
+
1891
+ next_lex, unrank_lex, cardinality, length, order, size
1892
+ """
1893
+ if self._rank is not None:
1894
+ return self._rank
1895
+ rank = 0
1896
+ rho = self.array_form[:]
1897
+ n = self.size - 1
1898
+ size = n + 1
1899
+ psize = int(ifac(n))
1900
+ for j in range(size - 1):
1901
+ rank += rho[j]*psize
1902
+ for i in range(j + 1, size):
1903
+ if rho[i] > rho[j]:
1904
+ rho[i] -= 1
1905
+ psize //= n
1906
+ n -= 1
1907
+ self._rank = rank
1908
+ return rank
1909
+
1910
+ @property
1911
+ def cardinality(self):
1912
+ """
1913
+ Returns the number of all possible permutations.
1914
+
1915
+ Examples
1916
+ ========
1917
+
1918
+ >>> from sympy.combinatorics import Permutation
1919
+ >>> p = Permutation([0, 1, 2, 3])
1920
+ >>> p.cardinality
1921
+ 24
1922
+
1923
+ See Also
1924
+ ========
1925
+
1926
+ length, order, rank, size
1927
+ """
1928
+ return int(ifac(self.size))
1929
+
1930
+ def parity(self):
1931
+ """
1932
+ Computes the parity of a permutation.
1933
+
1934
+ Explanation
1935
+ ===========
1936
+
1937
+ The parity of a permutation reflects the parity of the
1938
+ number of inversions in the permutation, i.e., the
1939
+ number of pairs of x and y such that ``x > y`` but ``p[x] < p[y]``.
1940
+
1941
+ Examples
1942
+ ========
1943
+
1944
+ >>> from sympy.combinatorics import Permutation
1945
+ >>> p = Permutation([0, 1, 2, 3])
1946
+ >>> p.parity()
1947
+ 0
1948
+ >>> p = Permutation([3, 2, 0, 1])
1949
+ >>> p.parity()
1950
+ 1
1951
+
1952
+ See Also
1953
+ ========
1954
+
1955
+ _af_parity
1956
+ """
1957
+ if self._cyclic_form is not None:
1958
+ return (self.size - self.cycles) % 2
1959
+
1960
+ return _af_parity(self.array_form)
1961
+
1962
+ @property
1963
+ def is_even(self):
1964
+ """
1965
+ Checks if a permutation is even.
1966
+
1967
+ Examples
1968
+ ========
1969
+
1970
+ >>> from sympy.combinatorics import Permutation
1971
+ >>> p = Permutation([0, 1, 2, 3])
1972
+ >>> p.is_even
1973
+ True
1974
+ >>> p = Permutation([3, 2, 1, 0])
1975
+ >>> p.is_even
1976
+ True
1977
+
1978
+ See Also
1979
+ ========
1980
+
1981
+ is_odd
1982
+ """
1983
+ return not self.is_odd
1984
+
1985
+ @property
1986
+ def is_odd(self):
1987
+ """
1988
+ Checks if a permutation is odd.
1989
+
1990
+ Examples
1991
+ ========
1992
+
1993
+ >>> from sympy.combinatorics import Permutation
1994
+ >>> p = Permutation([0, 1, 2, 3])
1995
+ >>> p.is_odd
1996
+ False
1997
+ >>> p = Permutation([3, 2, 0, 1])
1998
+ >>> p.is_odd
1999
+ True
2000
+
2001
+ See Also
2002
+ ========
2003
+
2004
+ is_even
2005
+ """
2006
+ return bool(self.parity() % 2)
2007
+
2008
+ @property
2009
+ def is_Singleton(self):
2010
+ """
2011
+ Checks to see if the permutation contains only one number and is
2012
+ thus the only possible permutation of this set of numbers
2013
+
2014
+ Examples
2015
+ ========
2016
+
2017
+ >>> from sympy.combinatorics import Permutation
2018
+ >>> Permutation([0]).is_Singleton
2019
+ True
2020
+ >>> Permutation([0, 1]).is_Singleton
2021
+ False
2022
+
2023
+ See Also
2024
+ ========
2025
+
2026
+ is_Empty
2027
+ """
2028
+ return self.size == 1
2029
+
2030
+ @property
2031
+ def is_Empty(self):
2032
+ """
2033
+ Checks to see if the permutation is a set with zero elements
2034
+
2035
+ Examples
2036
+ ========
2037
+
2038
+ >>> from sympy.combinatorics import Permutation
2039
+ >>> Permutation([]).is_Empty
2040
+ True
2041
+ >>> Permutation([0]).is_Empty
2042
+ False
2043
+
2044
+ See Also
2045
+ ========
2046
+
2047
+ is_Singleton
2048
+ """
2049
+ return self.size == 0
2050
+
2051
+ @property
2052
+ def is_identity(self):
2053
+ return self.is_Identity
2054
+
2055
+ @property
2056
+ def is_Identity(self):
2057
+ """
2058
+ Returns True if the Permutation is an identity permutation.
2059
+
2060
+ Examples
2061
+ ========
2062
+
2063
+ >>> from sympy.combinatorics import Permutation
2064
+ >>> p = Permutation([])
2065
+ >>> p.is_Identity
2066
+ True
2067
+ >>> p = Permutation([[0], [1], [2]])
2068
+ >>> p.is_Identity
2069
+ True
2070
+ >>> p = Permutation([0, 1, 2])
2071
+ >>> p.is_Identity
2072
+ True
2073
+ >>> p = Permutation([0, 2, 1])
2074
+ >>> p.is_Identity
2075
+ False
2076
+
2077
+ See Also
2078
+ ========
2079
+
2080
+ order
2081
+ """
2082
+ af = self.array_form
2083
+ return not af or all(i == af[i] for i in range(self.size))
2084
+
2085
+ def ascents(self):
2086
+ """
2087
+ Returns the positions of ascents in a permutation, ie, the location
2088
+ where p[i] < p[i+1]
2089
+
2090
+ Examples
2091
+ ========
2092
+
2093
+ >>> from sympy.combinatorics import Permutation
2094
+ >>> p = Permutation([4, 0, 1, 3, 2])
2095
+ >>> p.ascents()
2096
+ [1, 2]
2097
+
2098
+ See Also
2099
+ ========
2100
+
2101
+ descents, inversions, min, max
2102
+ """
2103
+ a = self.array_form
2104
+ pos = [i for i in range(len(a) - 1) if a[i] < a[i + 1]]
2105
+ return pos
2106
+
2107
+ def descents(self):
2108
+ """
2109
+ Returns the positions of descents in a permutation, ie, the location
2110
+ where p[i] > p[i+1]
2111
+
2112
+ Examples
2113
+ ========
2114
+
2115
+ >>> from sympy.combinatorics import Permutation
2116
+ >>> p = Permutation([4, 0, 1, 3, 2])
2117
+ >>> p.descents()
2118
+ [0, 3]
2119
+
2120
+ See Also
2121
+ ========
2122
+
2123
+ ascents, inversions, min, max
2124
+ """
2125
+ a = self.array_form
2126
+ pos = [i for i in range(len(a) - 1) if a[i] > a[i + 1]]
2127
+ return pos
2128
+
2129
+ def max(self) -> int:
2130
+ """
2131
+ The maximum element moved by the permutation.
2132
+
2133
+ Examples
2134
+ ========
2135
+
2136
+ >>> from sympy.combinatorics import Permutation
2137
+ >>> p = Permutation([1, 0, 2, 3, 4])
2138
+ >>> p.max()
2139
+ 1
2140
+
2141
+ See Also
2142
+ ========
2143
+
2144
+ min, descents, ascents, inversions
2145
+ """
2146
+ a = self.array_form
2147
+ if not a:
2148
+ return 0
2149
+ return max(_a for i, _a in enumerate(a) if _a != i)
2150
+
2151
+ def min(self) -> int:
2152
+ """
2153
+ The minimum element moved by the permutation.
2154
+
2155
+ Examples
2156
+ ========
2157
+
2158
+ >>> from sympy.combinatorics import Permutation
2159
+ >>> p = Permutation([0, 1, 4, 3, 2])
2160
+ >>> p.min()
2161
+ 2
2162
+
2163
+ See Also
2164
+ ========
2165
+
2166
+ max, descents, ascents, inversions
2167
+ """
2168
+ a = self.array_form
2169
+ if not a:
2170
+ return 0
2171
+ return min(_a for i, _a in enumerate(a) if _a != i)
2172
+
2173
+ def inversions(self):
2174
+ """
2175
+ Computes the number of inversions of a permutation.
2176
+
2177
+ Explanation
2178
+ ===========
2179
+
2180
+ An inversion is where i > j but p[i] < p[j].
2181
+
2182
+ For small length of p, it iterates over all i and j
2183
+ values and calculates the number of inversions.
2184
+ For large length of p, it uses a variation of merge
2185
+ sort to calculate the number of inversions.
2186
+
2187
+ Examples
2188
+ ========
2189
+
2190
+ >>> from sympy.combinatorics import Permutation
2191
+ >>> p = Permutation([0, 1, 2, 3, 4, 5])
2192
+ >>> p.inversions()
2193
+ 0
2194
+ >>> Permutation([3, 2, 1, 0]).inversions()
2195
+ 6
2196
+
2197
+ See Also
2198
+ ========
2199
+
2200
+ descents, ascents, min, max
2201
+
2202
+ References
2203
+ ==========
2204
+
2205
+ .. [1] https://www.cp.eng.chula.ac.th/~prabhas//teaching/algo/algo2008/count-inv.htm
2206
+
2207
+ """
2208
+ inversions = 0
2209
+ a = self.array_form
2210
+ n = len(a)
2211
+ if n < 130:
2212
+ for i in range(n - 1):
2213
+ b = a[i]
2214
+ for c in a[i + 1:]:
2215
+ if b > c:
2216
+ inversions += 1
2217
+ else:
2218
+ k = 1
2219
+ right = 0
2220
+ arr = a[:]
2221
+ temp = a[:]
2222
+ while k < n:
2223
+ i = 0
2224
+ while i + k < n:
2225
+ right = i + k * 2 - 1
2226
+ if right >= n:
2227
+ right = n - 1
2228
+ inversions += _merge(arr, temp, i, i + k, right)
2229
+ i = i + k * 2
2230
+ k = k * 2
2231
+ return inversions
2232
+
2233
+ def commutator(self, x):
2234
+ """Return the commutator of ``self`` and ``x``: ``~x*~self*x*self``
2235
+
2236
+ If f and g are part of a group, G, then the commutator of f and g
2237
+ is the group identity iff f and g commute, i.e. fg == gf.
2238
+
2239
+ Examples
2240
+ ========
2241
+
2242
+ >>> from sympy.combinatorics import Permutation
2243
+ >>> from sympy import init_printing
2244
+ >>> init_printing(perm_cyclic=False, pretty_print=False)
2245
+ >>> p = Permutation([0, 2, 3, 1])
2246
+ >>> x = Permutation([2, 0, 3, 1])
2247
+ >>> c = p.commutator(x); c
2248
+ Permutation([2, 1, 3, 0])
2249
+ >>> c == ~x*~p*x*p
2250
+ True
2251
+
2252
+ >>> I = Permutation(3)
2253
+ >>> p = [I + i for i in range(6)]
2254
+ >>> for i in range(len(p)):
2255
+ ... for j in range(len(p)):
2256
+ ... c = p[i].commutator(p[j])
2257
+ ... if p[i]*p[j] == p[j]*p[i]:
2258
+ ... assert c == I
2259
+ ... else:
2260
+ ... assert c != I
2261
+ ...
2262
+
2263
+ References
2264
+ ==========
2265
+
2266
+ .. [1] https://en.wikipedia.org/wiki/Commutator
2267
+ """
2268
+
2269
+ a = self.array_form
2270
+ b = x.array_form
2271
+ n = len(a)
2272
+ if len(b) != n:
2273
+ raise ValueError("The permutations must be of equal size.")
2274
+ inva = [None]*n
2275
+ for i in range(n):
2276
+ inva[a[i]] = i
2277
+ invb = [None]*n
2278
+ for i in range(n):
2279
+ invb[b[i]] = i
2280
+ return self._af_new([a[b[inva[i]]] for i in invb])
2281
+
2282
+ def signature(self):
2283
+ """
2284
+ Gives the signature of the permutation needed to place the
2285
+ elements of the permutation in canonical order.
2286
+
2287
+ The signature is calculated as (-1)^<number of inversions>
2288
+
2289
+ Examples
2290
+ ========
2291
+
2292
+ >>> from sympy.combinatorics import Permutation
2293
+ >>> p = Permutation([0, 1, 2])
2294
+ >>> p.inversions()
2295
+ 0
2296
+ >>> p.signature()
2297
+ 1
2298
+ >>> q = Permutation([0,2,1])
2299
+ >>> q.inversions()
2300
+ 1
2301
+ >>> q.signature()
2302
+ -1
2303
+
2304
+ See Also
2305
+ ========
2306
+
2307
+ inversions
2308
+ """
2309
+ if self.is_even:
2310
+ return 1
2311
+ return -1
2312
+
2313
+ def order(self):
2314
+ """
2315
+ Computes the order of a permutation.
2316
+
2317
+ When the permutation is raised to the power of its
2318
+ order it equals the identity permutation.
2319
+
2320
+ Examples
2321
+ ========
2322
+
2323
+ >>> from sympy.combinatorics import Permutation
2324
+ >>> from sympy import init_printing
2325
+ >>> init_printing(perm_cyclic=False, pretty_print=False)
2326
+ >>> p = Permutation([3, 1, 5, 2, 4, 0])
2327
+ >>> p.order()
2328
+ 4
2329
+ >>> (p**(p.order()))
2330
+ Permutation([], size=6)
2331
+
2332
+ See Also
2333
+ ========
2334
+
2335
+ identity, cardinality, length, rank, size
2336
+ """
2337
+
2338
+ return reduce(lcm, [len(cycle) for cycle in self.cyclic_form], 1)
2339
+
2340
+ def length(self):
2341
+ """
2342
+ Returns the number of integers moved by a permutation.
2343
+
2344
+ Examples
2345
+ ========
2346
+
2347
+ >>> from sympy.combinatorics import Permutation
2348
+ >>> Permutation([0, 3, 2, 1]).length()
2349
+ 2
2350
+ >>> Permutation([[0, 1], [2, 3]]).length()
2351
+ 4
2352
+
2353
+ See Also
2354
+ ========
2355
+
2356
+ min, max, support, cardinality, order, rank, size
2357
+ """
2358
+
2359
+ return len(self.support())
2360
+
2361
+ @property
2362
+ def cycle_structure(self):
2363
+ """Return the cycle structure of the permutation as a dictionary
2364
+ indicating the multiplicity of each cycle length.
2365
+
2366
+ Examples
2367
+ ========
2368
+
2369
+ >>> from sympy.combinatorics import Permutation
2370
+ >>> Permutation(3).cycle_structure
2371
+ {1: 4}
2372
+ >>> Permutation(0, 4, 3)(1, 2)(5, 6).cycle_structure
2373
+ {2: 2, 3: 1}
2374
+ """
2375
+ if self._cycle_structure:
2376
+ rv = self._cycle_structure
2377
+ else:
2378
+ rv = defaultdict(int)
2379
+ singletons = self.size
2380
+ for c in self.cyclic_form:
2381
+ rv[len(c)] += 1
2382
+ singletons -= len(c)
2383
+ if singletons:
2384
+ rv[1] = singletons
2385
+ self._cycle_structure = rv
2386
+ return dict(rv) # make a copy
2387
+
2388
+ @property
2389
+ def cycles(self):
2390
+ """
2391
+ Returns the number of cycles contained in the permutation
2392
+ (including singletons).
2393
+
2394
+ Examples
2395
+ ========
2396
+
2397
+ >>> from sympy.combinatorics import Permutation
2398
+ >>> Permutation([0, 1, 2]).cycles
2399
+ 3
2400
+ >>> Permutation([0, 1, 2]).full_cyclic_form
2401
+ [[0], [1], [2]]
2402
+ >>> Permutation(0, 1)(2, 3).cycles
2403
+ 2
2404
+
2405
+ See Also
2406
+ ========
2407
+ sympy.functions.combinatorial.numbers.stirling
2408
+ """
2409
+ return len(self.full_cyclic_form)
2410
+
2411
+ def index(self):
2412
+ """
2413
+ Returns the index of a permutation.
2414
+
2415
+ The index of a permutation is the sum of all subscripts j such
2416
+ that p[j] is greater than p[j+1].
2417
+
2418
+ Examples
2419
+ ========
2420
+
2421
+ >>> from sympy.combinatorics import Permutation
2422
+ >>> p = Permutation([3, 0, 2, 1, 4])
2423
+ >>> p.index()
2424
+ 2
2425
+ """
2426
+ a = self.array_form
2427
+
2428
+ return sum(j for j in range(len(a) - 1) if a[j] > a[j + 1])
2429
+
2430
+ def runs(self):
2431
+ """
2432
+ Returns the runs of a permutation.
2433
+
2434
+ An ascending sequence in a permutation is called a run [5].
2435
+
2436
+
2437
+ Examples
2438
+ ========
2439
+
2440
+ >>> from sympy.combinatorics import Permutation
2441
+ >>> p = Permutation([2, 5, 7, 3, 6, 0, 1, 4, 8])
2442
+ >>> p.runs()
2443
+ [[2, 5, 7], [3, 6], [0, 1, 4, 8]]
2444
+ >>> q = Permutation([1,3,2,0])
2445
+ >>> q.runs()
2446
+ [[1, 3], [2], [0]]
2447
+ """
2448
+ return runs(self.array_form)
2449
+
2450
+ def inversion_vector(self):
2451
+ """Return the inversion vector of the permutation.
2452
+
2453
+ The inversion vector consists of elements whose value
2454
+ indicates the number of elements in the permutation
2455
+ that are lesser than it and lie on its right hand side.
2456
+
2457
+ The inversion vector is the same as the Lehmer encoding of a
2458
+ permutation.
2459
+
2460
+ Examples
2461
+ ========
2462
+
2463
+ >>> from sympy.combinatorics import Permutation
2464
+ >>> p = Permutation([4, 8, 0, 7, 1, 5, 3, 6, 2])
2465
+ >>> p.inversion_vector()
2466
+ [4, 7, 0, 5, 0, 2, 1, 1]
2467
+ >>> p = Permutation([3, 2, 1, 0])
2468
+ >>> p.inversion_vector()
2469
+ [3, 2, 1]
2470
+
2471
+ The inversion vector increases lexicographically with the rank
2472
+ of the permutation, the -ith element cycling through 0..i.
2473
+
2474
+ >>> p = Permutation(2)
2475
+ >>> while p:
2476
+ ... print('%s %s %s' % (p, p.inversion_vector(), p.rank()))
2477
+ ... p = p.next_lex()
2478
+ (2) [0, 0] 0
2479
+ (1 2) [0, 1] 1
2480
+ (2)(0 1) [1, 0] 2
2481
+ (0 1 2) [1, 1] 3
2482
+ (0 2 1) [2, 0] 4
2483
+ (0 2) [2, 1] 5
2484
+
2485
+ See Also
2486
+ ========
2487
+
2488
+ from_inversion_vector
2489
+ """
2490
+ self_array_form = self.array_form
2491
+ n = len(self_array_form)
2492
+ inversion_vector = [0] * (n - 1)
2493
+
2494
+ for i in range(n - 1):
2495
+ val = 0
2496
+ for j in range(i + 1, n):
2497
+ if self_array_form[j] < self_array_form[i]:
2498
+ val += 1
2499
+ inversion_vector[i] = val
2500
+ return inversion_vector
2501
+
2502
+ def rank_trotterjohnson(self):
2503
+ """
2504
+ Returns the Trotter Johnson rank, which we get from the minimal
2505
+ change algorithm. See [4] section 2.4.
2506
+
2507
+ Examples
2508
+ ========
2509
+
2510
+ >>> from sympy.combinatorics import Permutation
2511
+ >>> p = Permutation([0, 1, 2, 3])
2512
+ >>> p.rank_trotterjohnson()
2513
+ 0
2514
+ >>> p = Permutation([0, 2, 1, 3])
2515
+ >>> p.rank_trotterjohnson()
2516
+ 7
2517
+
2518
+ See Also
2519
+ ========
2520
+
2521
+ unrank_trotterjohnson, next_trotterjohnson
2522
+ """
2523
+ if self.array_form == [] or self.is_Identity:
2524
+ return 0
2525
+ if self.array_form == [1, 0]:
2526
+ return 1
2527
+ perm = self.array_form
2528
+ n = self.size
2529
+ rank = 0
2530
+ for j in range(1, n):
2531
+ k = 1
2532
+ i = 0
2533
+ while perm[i] != j:
2534
+ if perm[i] < j:
2535
+ k += 1
2536
+ i += 1
2537
+ j1 = j + 1
2538
+ if rank % 2 == 0:
2539
+ rank = j1*rank + j1 - k
2540
+ else:
2541
+ rank = j1*rank + k - 1
2542
+ return rank
2543
+
2544
+ @classmethod
2545
+ def unrank_trotterjohnson(cls, size, rank):
2546
+ """
2547
+ Trotter Johnson permutation unranking. See [4] section 2.4.
2548
+
2549
+ Examples
2550
+ ========
2551
+
2552
+ >>> from sympy.combinatorics import Permutation
2553
+ >>> from sympy import init_printing
2554
+ >>> init_printing(perm_cyclic=False, pretty_print=False)
2555
+ >>> Permutation.unrank_trotterjohnson(5, 10)
2556
+ Permutation([0, 3, 1, 2, 4])
2557
+
2558
+ See Also
2559
+ ========
2560
+
2561
+ rank_trotterjohnson, next_trotterjohnson
2562
+ """
2563
+ perm = [0]*size
2564
+ r2 = 0
2565
+ n = ifac(size)
2566
+ pj = 1
2567
+ for j in range(2, size + 1):
2568
+ pj *= j
2569
+ r1 = (rank * pj) // n
2570
+ k = r1 - j*r2
2571
+ if r2 % 2 == 0:
2572
+ for i in range(j - 1, j - k - 1, -1):
2573
+ perm[i] = perm[i - 1]
2574
+ perm[j - k - 1] = j - 1
2575
+ else:
2576
+ for i in range(j - 1, k, -1):
2577
+ perm[i] = perm[i - 1]
2578
+ perm[k] = j - 1
2579
+ r2 = r1
2580
+ return cls._af_new(perm)
2581
+
2582
+ def next_trotterjohnson(self):
2583
+ """
2584
+ Returns the next permutation in Trotter-Johnson order.
2585
+ If self is the last permutation it returns None.
2586
+ See [4] section 2.4. If it is desired to generate all such
2587
+ permutations, they can be generated in order more quickly
2588
+ with the ``generate_bell`` function.
2589
+
2590
+ Examples
2591
+ ========
2592
+
2593
+ >>> from sympy.combinatorics import Permutation
2594
+ >>> from sympy import init_printing
2595
+ >>> init_printing(perm_cyclic=False, pretty_print=False)
2596
+ >>> p = Permutation([3, 0, 2, 1])
2597
+ >>> p.rank_trotterjohnson()
2598
+ 4
2599
+ >>> p = p.next_trotterjohnson(); p
2600
+ Permutation([0, 3, 2, 1])
2601
+ >>> p.rank_trotterjohnson()
2602
+ 5
2603
+
2604
+ See Also
2605
+ ========
2606
+
2607
+ rank_trotterjohnson, unrank_trotterjohnson, sympy.utilities.iterables.generate_bell
2608
+ """
2609
+ pi = self.array_form[:]
2610
+ n = len(pi)
2611
+ st = 0
2612
+ rho = pi[:]
2613
+ done = False
2614
+ m = n-1
2615
+ while m > 0 and not done:
2616
+ d = rho.index(m)
2617
+ for i in range(d, m):
2618
+ rho[i] = rho[i + 1]
2619
+ par = _af_parity(rho[:m])
2620
+ if par == 1:
2621
+ if d == m:
2622
+ m -= 1
2623
+ else:
2624
+ pi[st + d], pi[st + d + 1] = pi[st + d + 1], pi[st + d]
2625
+ done = True
2626
+ else:
2627
+ if d == 0:
2628
+ m -= 1
2629
+ st += 1
2630
+ else:
2631
+ pi[st + d], pi[st + d - 1] = pi[st + d - 1], pi[st + d]
2632
+ done = True
2633
+ if m == 0:
2634
+ return None
2635
+ return self._af_new(pi)
2636
+
2637
+ def get_precedence_matrix(self):
2638
+ """
2639
+ Gets the precedence matrix. This is used for computing the
2640
+ distance between two permutations.
2641
+
2642
+ Examples
2643
+ ========
2644
+
2645
+ >>> from sympy.combinatorics import Permutation
2646
+ >>> from sympy import init_printing
2647
+ >>> init_printing(perm_cyclic=False, pretty_print=False)
2648
+ >>> p = Permutation.josephus(3, 6, 1)
2649
+ >>> p
2650
+ Permutation([2, 5, 3, 1, 4, 0])
2651
+ >>> p.get_precedence_matrix()
2652
+ Matrix([
2653
+ [0, 0, 0, 0, 0, 0],
2654
+ [1, 0, 0, 0, 1, 0],
2655
+ [1, 1, 0, 1, 1, 1],
2656
+ [1, 1, 0, 0, 1, 0],
2657
+ [1, 0, 0, 0, 0, 0],
2658
+ [1, 1, 0, 1, 1, 0]])
2659
+
2660
+ See Also
2661
+ ========
2662
+
2663
+ get_precedence_distance, get_adjacency_matrix, get_adjacency_distance
2664
+ """
2665
+ m = zeros(self.size)
2666
+ perm = self.array_form
2667
+ for i in range(m.rows):
2668
+ for j in range(i + 1, m.cols):
2669
+ m[perm[i], perm[j]] = 1
2670
+ return m
2671
+
2672
+ def get_precedence_distance(self, other):
2673
+ """
2674
+ Computes the precedence distance between two permutations.
2675
+
2676
+ Explanation
2677
+ ===========
2678
+
2679
+ Suppose p and p' represent n jobs. The precedence metric
2680
+ counts the number of times a job j is preceded by job i
2681
+ in both p and p'. This metric is commutative.
2682
+
2683
+ Examples
2684
+ ========
2685
+
2686
+ >>> from sympy.combinatorics import Permutation
2687
+ >>> p = Permutation([2, 0, 4, 3, 1])
2688
+ >>> q = Permutation([3, 1, 2, 4, 0])
2689
+ >>> p.get_precedence_distance(q)
2690
+ 7
2691
+ >>> q.get_precedence_distance(p)
2692
+ 7
2693
+
2694
+ See Also
2695
+ ========
2696
+
2697
+ get_precedence_matrix, get_adjacency_matrix, get_adjacency_distance
2698
+ """
2699
+ if self.size != other.size:
2700
+ raise ValueError("The permutations must be of equal size.")
2701
+ self_prec_mat = self.get_precedence_matrix()
2702
+ other_prec_mat = other.get_precedence_matrix()
2703
+ n_prec = 0
2704
+ for i in range(self.size):
2705
+ for j in range(self.size):
2706
+ if i == j:
2707
+ continue
2708
+ if self_prec_mat[i, j] * other_prec_mat[i, j] == 1:
2709
+ n_prec += 1
2710
+ d = self.size * (self.size - 1)//2 - n_prec
2711
+ return d
2712
+
2713
+ def get_adjacency_matrix(self):
2714
+ """
2715
+ Computes the adjacency matrix of a permutation.
2716
+
2717
+ Explanation
2718
+ ===========
2719
+
2720
+ If job i is adjacent to job j in a permutation p
2721
+ then we set m[i, j] = 1 where m is the adjacency
2722
+ matrix of p.
2723
+
2724
+ Examples
2725
+ ========
2726
+
2727
+ >>> from sympy.combinatorics import Permutation
2728
+ >>> p = Permutation.josephus(3, 6, 1)
2729
+ >>> p.get_adjacency_matrix()
2730
+ Matrix([
2731
+ [0, 0, 0, 0, 0, 0],
2732
+ [0, 0, 0, 0, 1, 0],
2733
+ [0, 0, 0, 0, 0, 1],
2734
+ [0, 1, 0, 0, 0, 0],
2735
+ [1, 0, 0, 0, 0, 0],
2736
+ [0, 0, 0, 1, 0, 0]])
2737
+ >>> q = Permutation([0, 1, 2, 3])
2738
+ >>> q.get_adjacency_matrix()
2739
+ Matrix([
2740
+ [0, 1, 0, 0],
2741
+ [0, 0, 1, 0],
2742
+ [0, 0, 0, 1],
2743
+ [0, 0, 0, 0]])
2744
+
2745
+ See Also
2746
+ ========
2747
+
2748
+ get_precedence_matrix, get_precedence_distance, get_adjacency_distance
2749
+ """
2750
+ m = zeros(self.size)
2751
+ perm = self.array_form
2752
+ for i in range(self.size - 1):
2753
+ m[perm[i], perm[i + 1]] = 1
2754
+ return m
2755
+
2756
+ def get_adjacency_distance(self, other):
2757
+ """
2758
+ Computes the adjacency distance between two permutations.
2759
+
2760
+ Explanation
2761
+ ===========
2762
+
2763
+ This metric counts the number of times a pair i,j of jobs is
2764
+ adjacent in both p and p'. If n_adj is this quantity then
2765
+ the adjacency distance is n - n_adj - 1 [1]
2766
+
2767
+ [1] Reeves, Colin R. Landscapes, Operators and Heuristic search, Annals
2768
+ of Operational Research, 86, pp 473-490. (1999)
2769
+
2770
+
2771
+ Examples
2772
+ ========
2773
+
2774
+ >>> from sympy.combinatorics import Permutation
2775
+ >>> p = Permutation([0, 3, 1, 2, 4])
2776
+ >>> q = Permutation.josephus(4, 5, 2)
2777
+ >>> p.get_adjacency_distance(q)
2778
+ 3
2779
+ >>> r = Permutation([0, 2, 1, 4, 3])
2780
+ >>> p.get_adjacency_distance(r)
2781
+ 4
2782
+
2783
+ See Also
2784
+ ========
2785
+
2786
+ get_precedence_matrix, get_precedence_distance, get_adjacency_matrix
2787
+ """
2788
+ if self.size != other.size:
2789
+ raise ValueError("The permutations must be of the same size.")
2790
+ self_adj_mat = self.get_adjacency_matrix()
2791
+ other_adj_mat = other.get_adjacency_matrix()
2792
+ n_adj = 0
2793
+ for i in range(self.size):
2794
+ for j in range(self.size):
2795
+ if i == j:
2796
+ continue
2797
+ if self_adj_mat[i, j] * other_adj_mat[i, j] == 1:
2798
+ n_adj += 1
2799
+ d = self.size - n_adj - 1
2800
+ return d
2801
+
2802
+ def get_positional_distance(self, other):
2803
+ """
2804
+ Computes the positional distance between two permutations.
2805
+
2806
+ Examples
2807
+ ========
2808
+
2809
+ >>> from sympy.combinatorics import Permutation
2810
+ >>> p = Permutation([0, 3, 1, 2, 4])
2811
+ >>> q = Permutation.josephus(4, 5, 2)
2812
+ >>> r = Permutation([3, 1, 4, 0, 2])
2813
+ >>> p.get_positional_distance(q)
2814
+ 12
2815
+ >>> p.get_positional_distance(r)
2816
+ 12
2817
+
2818
+ See Also
2819
+ ========
2820
+
2821
+ get_precedence_distance, get_adjacency_distance
2822
+ """
2823
+ a = self.array_form
2824
+ b = other.array_form
2825
+ if len(a) != len(b):
2826
+ raise ValueError("The permutations must be of the same size.")
2827
+ return sum(abs(a[i] - b[i]) for i in range(len(a)))
2828
+
2829
+ @classmethod
2830
+ def josephus(cls, m, n, s=1):
2831
+ """Return as a permutation the shuffling of range(n) using the Josephus
2832
+ scheme in which every m-th item is selected until all have been chosen.
2833
+ The returned permutation has elements listed by the order in which they
2834
+ were selected.
2835
+
2836
+ The parameter ``s`` stops the selection process when there are ``s``
2837
+ items remaining and these are selected by continuing the selection,
2838
+ counting by 1 rather than by ``m``.
2839
+
2840
+ Consider selecting every 3rd item from 6 until only 2 remain::
2841
+
2842
+ choices chosen
2843
+ ======== ======
2844
+ 012345
2845
+ 01 345 2
2846
+ 01 34 25
2847
+ 01 4 253
2848
+ 0 4 2531
2849
+ 0 25314
2850
+ 253140
2851
+
2852
+ Examples
2853
+ ========
2854
+
2855
+ >>> from sympy.combinatorics import Permutation
2856
+ >>> Permutation.josephus(3, 6, 2).array_form
2857
+ [2, 5, 3, 1, 4, 0]
2858
+
2859
+ References
2860
+ ==========
2861
+
2862
+ .. [1] https://en.wikipedia.org/wiki/Flavius_Josephus
2863
+ .. [2] https://en.wikipedia.org/wiki/Josephus_problem
2864
+ .. [3] https://web.archive.org/web/20171008094331/http://www.wou.edu/~burtonl/josephus.html
2865
+
2866
+ """
2867
+ from collections import deque
2868
+ m -= 1
2869
+ Q = deque(list(range(n)))
2870
+ perm = []
2871
+ while len(Q) > max(s, 1):
2872
+ for dp in range(m):
2873
+ Q.append(Q.popleft())
2874
+ perm.append(Q.popleft())
2875
+ perm.extend(list(Q))
2876
+ return cls(perm)
2877
+
2878
+ @classmethod
2879
+ def from_inversion_vector(cls, inversion):
2880
+ """
2881
+ Calculates the permutation from the inversion vector.
2882
+
2883
+ Examples
2884
+ ========
2885
+
2886
+ >>> from sympy.combinatorics import Permutation
2887
+ >>> from sympy import init_printing
2888
+ >>> init_printing(perm_cyclic=False, pretty_print=False)
2889
+ >>> Permutation.from_inversion_vector([3, 2, 1, 0, 0])
2890
+ Permutation([3, 2, 1, 0, 4, 5])
2891
+
2892
+ """
2893
+ size = len(inversion)
2894
+ N = list(range(size + 1))
2895
+ perm = []
2896
+ try:
2897
+ for k in range(size):
2898
+ val = N[inversion[k]]
2899
+ perm.append(val)
2900
+ N.remove(val)
2901
+ except IndexError:
2902
+ raise ValueError("The inversion vector is not valid.")
2903
+ perm.extend(N)
2904
+ return cls._af_new(perm)
2905
+
2906
+ @classmethod
2907
+ def random(cls, n):
2908
+ """
2909
+ Generates a random permutation of length ``n``.
2910
+
2911
+ Uses the underlying Python pseudo-random number generator.
2912
+
2913
+ Examples
2914
+ ========
2915
+
2916
+ >>> from sympy.combinatorics import Permutation
2917
+ >>> Permutation.random(2) in (Permutation([1, 0]), Permutation([0, 1]))
2918
+ True
2919
+
2920
+ """
2921
+ perm_array = list(range(n))
2922
+ random.shuffle(perm_array)
2923
+ return cls._af_new(perm_array)
2924
+
2925
+ @classmethod
2926
+ def unrank_lex(cls, size, rank):
2927
+ """
2928
+ Lexicographic permutation unranking.
2929
+
2930
+ Examples
2931
+ ========
2932
+
2933
+ >>> from sympy.combinatorics import Permutation
2934
+ >>> from sympy import init_printing
2935
+ >>> init_printing(perm_cyclic=False, pretty_print=False)
2936
+ >>> a = Permutation.unrank_lex(5, 10)
2937
+ >>> a.rank()
2938
+ 10
2939
+ >>> a
2940
+ Permutation([0, 2, 4, 1, 3])
2941
+
2942
+ See Also
2943
+ ========
2944
+
2945
+ rank, next_lex
2946
+ """
2947
+ perm_array = [0] * size
2948
+ psize = 1
2949
+ for i in range(size):
2950
+ new_psize = psize*(i + 1)
2951
+ d = (rank % new_psize) // psize
2952
+ rank -= d*psize
2953
+ perm_array[size - i - 1] = d
2954
+ for j in range(size - i, size):
2955
+ if perm_array[j] > d - 1:
2956
+ perm_array[j] += 1
2957
+ psize = new_psize
2958
+ return cls._af_new(perm_array)
2959
+
2960
+ def resize(self, n):
2961
+ """Resize the permutation to the new size ``n``.
2962
+
2963
+ Parameters
2964
+ ==========
2965
+
2966
+ n : int
2967
+ The new size of the permutation.
2968
+
2969
+ Raises
2970
+ ======
2971
+
2972
+ ValueError
2973
+ If the permutation cannot be resized to the given size.
2974
+ This may only happen when resized to a smaller size than
2975
+ the original.
2976
+
2977
+ Examples
2978
+ ========
2979
+
2980
+ >>> from sympy.combinatorics import Permutation
2981
+
2982
+ Increasing the size of a permutation:
2983
+
2984
+ >>> p = Permutation(0, 1, 2)
2985
+ >>> p = p.resize(5)
2986
+ >>> p
2987
+ (4)(0 1 2)
2988
+
2989
+ Decreasing the size of the permutation:
2990
+
2991
+ >>> p = p.resize(4)
2992
+ >>> p
2993
+ (3)(0 1 2)
2994
+
2995
+ If resizing to the specific size breaks the cycles:
2996
+
2997
+ >>> p.resize(2)
2998
+ Traceback (most recent call last):
2999
+ ...
3000
+ ValueError: The permutation cannot be resized to 2 because the
3001
+ cycle (0, 1, 2) may break.
3002
+ """
3003
+ aform = self.array_form
3004
+ l = len(aform)
3005
+ if n > l:
3006
+ aform += list(range(l, n))
3007
+ return Permutation._af_new(aform)
3008
+
3009
+ elif n < l:
3010
+ cyclic_form = self.full_cyclic_form
3011
+ new_cyclic_form = []
3012
+ for cycle in cyclic_form:
3013
+ cycle_min = min(cycle)
3014
+ cycle_max = max(cycle)
3015
+ if cycle_min <= n-1:
3016
+ if cycle_max > n-1:
3017
+ raise ValueError(
3018
+ "The permutation cannot be resized to {} "
3019
+ "because the cycle {} may break."
3020
+ .format(n, tuple(cycle)))
3021
+
3022
+ new_cyclic_form.append(cycle)
3023
+ return Permutation(new_cyclic_form)
3024
+
3025
+ return self
3026
+
3027
+ # XXX Deprecated flag
3028
+ print_cyclic = None
3029
+
3030
+
3031
+ def _merge(arr, temp, left, mid, right):
3032
+ """
3033
+ Merges two sorted arrays and calculates the inversion count.
3034
+
3035
+ Helper function for calculating inversions. This method is
3036
+ for internal use only.
3037
+ """
3038
+ i = k = left
3039
+ j = mid
3040
+ inv_count = 0
3041
+ while i < mid and j <= right:
3042
+ if arr[i] < arr[j]:
3043
+ temp[k] = arr[i]
3044
+ k += 1
3045
+ i += 1
3046
+ else:
3047
+ temp[k] = arr[j]
3048
+ k += 1
3049
+ j += 1
3050
+ inv_count += (mid -i)
3051
+ while i < mid:
3052
+ temp[k] = arr[i]
3053
+ k += 1
3054
+ i += 1
3055
+ if j <= right:
3056
+ k += right - j + 1
3057
+ j += right - j + 1
3058
+ arr[left:k + 1] = temp[left:k + 1]
3059
+ else:
3060
+ arr[left:right + 1] = temp[left:right + 1]
3061
+ return inv_count
3062
+
3063
+ Perm = Permutation
3064
+ _af_new = Perm._af_new
3065
+
3066
+
3067
+ class AppliedPermutation(Expr):
3068
+ """A permutation applied to a symbolic variable.
3069
+
3070
+ Parameters
3071
+ ==========
3072
+
3073
+ perm : Permutation
3074
+ x : Expr
3075
+
3076
+ Examples
3077
+ ========
3078
+
3079
+ >>> from sympy import Symbol
3080
+ >>> from sympy.combinatorics import Permutation
3081
+
3082
+ Creating a symbolic permutation function application:
3083
+
3084
+ >>> x = Symbol('x')
3085
+ >>> p = Permutation(0, 1, 2)
3086
+ >>> p.apply(x)
3087
+ AppliedPermutation((0 1 2), x)
3088
+ >>> _.subs(x, 1)
3089
+ 2
3090
+ """
3091
+ def __new__(cls, perm, x, evaluate=None):
3092
+ if evaluate is None:
3093
+ evaluate = global_parameters.evaluate
3094
+
3095
+ perm = _sympify(perm)
3096
+ x = _sympify(x)
3097
+
3098
+ if not isinstance(perm, Permutation):
3099
+ raise ValueError("{} must be a Permutation instance."
3100
+ .format(perm))
3101
+
3102
+ if evaluate:
3103
+ if x.is_Integer:
3104
+ return perm.apply(x)
3105
+
3106
+ obj = super().__new__(cls, perm, x)
3107
+ return obj
3108
+
3109
+
3110
+ @dispatch(Permutation, Permutation)
3111
+ def _eval_is_eq(lhs, rhs):
3112
+ if lhs._size != rhs._size:
3113
+ return None
3114
+ return lhs._array_form == rhs._array_form
.venv/lib/python3.13/site-packages/sympy/combinatorics/polyhedron.py ADDED
@@ -0,0 +1,1019 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sympy.combinatorics import Permutation as Perm
2
+ from sympy.combinatorics.perm_groups import PermutationGroup
3
+ from sympy.core import Basic, Tuple, default_sort_key
4
+ from sympy.sets import FiniteSet
5
+ from sympy.utilities.iterables import (minlex, unflatten, flatten)
6
+ from sympy.utilities.misc import as_int
7
+
8
+ rmul = Perm.rmul
9
+
10
+
11
+ class Polyhedron(Basic):
12
+ """
13
+ Represents the polyhedral symmetry group (PSG).
14
+
15
+ Explanation
16
+ ===========
17
+
18
+ The PSG is one of the symmetry groups of the Platonic solids.
19
+ There are three polyhedral groups: the tetrahedral group
20
+ of order 12, the octahedral group of order 24, and the
21
+ icosahedral group of order 60.
22
+
23
+ All doctests have been given in the docstring of the
24
+ constructor of the object.
25
+
26
+ References
27
+ ==========
28
+
29
+ .. [1] https://mathworld.wolfram.com/PolyhedralGroup.html
30
+
31
+ """
32
+ _edges = None
33
+
34
+ def __new__(cls, corners, faces=(), pgroup=()):
35
+ """
36
+ The constructor of the Polyhedron group object.
37
+
38
+ Explanation
39
+ ===========
40
+
41
+ It takes up to three parameters: the corners, faces, and
42
+ allowed transformations.
43
+
44
+ The corners/vertices are entered as a list of arbitrary
45
+ expressions that are used to identify each vertex.
46
+
47
+ The faces are entered as a list of tuples of indices; a tuple
48
+ of indices identifies the vertices which define the face. They
49
+ should be entered in a cw or ccw order; they will be standardized
50
+ by reversal and rotation to be give the lowest lexical ordering.
51
+ If no faces are given then no edges will be computed.
52
+
53
+ >>> from sympy.combinatorics.polyhedron import Polyhedron
54
+ >>> Polyhedron(list('abc'), [(1, 2, 0)]).faces
55
+ {(0, 1, 2)}
56
+ >>> Polyhedron(list('abc'), [(1, 0, 2)]).faces
57
+ {(0, 1, 2)}
58
+
59
+ The allowed transformations are entered as allowable permutations
60
+ of the vertices for the polyhedron. Instance of Permutations
61
+ (as with faces) should refer to the supplied vertices by index.
62
+ These permutation are stored as a PermutationGroup.
63
+
64
+ Examples
65
+ ========
66
+
67
+ >>> from sympy.combinatorics.permutations import Permutation
68
+ >>> from sympy import init_printing
69
+ >>> from sympy.abc import w, x, y, z
70
+ >>> init_printing(pretty_print=False, perm_cyclic=False)
71
+
72
+ Here we construct the Polyhedron object for a tetrahedron.
73
+
74
+ >>> corners = [w, x, y, z]
75
+ >>> faces = [(0, 1, 2), (0, 2, 3), (0, 3, 1), (1, 2, 3)]
76
+
77
+ Next, allowed transformations of the polyhedron must be given. This
78
+ is given as permutations of vertices.
79
+
80
+ Although the vertices of a tetrahedron can be numbered in 24 (4!)
81
+ different ways, there are only 12 different orientations for a
82
+ physical tetrahedron. The following permutations, applied once or
83
+ twice, will generate all 12 of the orientations. (The identity
84
+ permutation, Permutation(range(4)), is not included since it does
85
+ not change the orientation of the vertices.)
86
+
87
+ >>> pgroup = [Permutation([[0, 1, 2], [3]]), \
88
+ Permutation([[0, 1, 3], [2]]), \
89
+ Permutation([[0, 2, 3], [1]]), \
90
+ Permutation([[1, 2, 3], [0]]), \
91
+ Permutation([[0, 1], [2, 3]]), \
92
+ Permutation([[0, 2], [1, 3]]), \
93
+ Permutation([[0, 3], [1, 2]])]
94
+
95
+ The Polyhedron is now constructed and demonstrated:
96
+
97
+ >>> tetra = Polyhedron(corners, faces, pgroup)
98
+ >>> tetra.size
99
+ 4
100
+ >>> tetra.edges
101
+ {(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)}
102
+ >>> tetra.corners
103
+ (w, x, y, z)
104
+
105
+ It can be rotated with an arbitrary permutation of vertices, e.g.
106
+ the following permutation is not in the pgroup:
107
+
108
+ >>> tetra.rotate(Permutation([0, 1, 3, 2]))
109
+ >>> tetra.corners
110
+ (w, x, z, y)
111
+
112
+ An allowed permutation of the vertices can be constructed by
113
+ repeatedly applying permutations from the pgroup to the vertices.
114
+ Here is a demonstration that applying p and p**2 for every p in
115
+ pgroup generates all the orientations of a tetrahedron and no others:
116
+
117
+ >>> all = ( (w, x, y, z), \
118
+ (x, y, w, z), \
119
+ (y, w, x, z), \
120
+ (w, z, x, y), \
121
+ (z, w, y, x), \
122
+ (w, y, z, x), \
123
+ (y, z, w, x), \
124
+ (x, z, y, w), \
125
+ (z, y, x, w), \
126
+ (y, x, z, w), \
127
+ (x, w, z, y), \
128
+ (z, x, w, y) )
129
+
130
+ >>> got = []
131
+ >>> for p in (pgroup + [p**2 for p in pgroup]):
132
+ ... h = Polyhedron(corners)
133
+ ... h.rotate(p)
134
+ ... got.append(h.corners)
135
+ ...
136
+ >>> set(got) == set(all)
137
+ True
138
+
139
+ The make_perm method of a PermutationGroup will randomly pick
140
+ permutations, multiply them together, and return the permutation that
141
+ can be applied to the polyhedron to give the orientation produced
142
+ by those individual permutations.
143
+
144
+ Here, 3 permutations are used:
145
+
146
+ >>> tetra.pgroup.make_perm(3) # doctest: +SKIP
147
+ Permutation([0, 3, 1, 2])
148
+
149
+ To select the permutations that should be used, supply a list
150
+ of indices to the permutations in pgroup in the order they should
151
+ be applied:
152
+
153
+ >>> use = [0, 0, 2]
154
+ >>> p002 = tetra.pgroup.make_perm(3, use)
155
+ >>> p002
156
+ Permutation([1, 0, 3, 2])
157
+
158
+
159
+ Apply them one at a time:
160
+
161
+ >>> tetra.reset()
162
+ >>> for i in use:
163
+ ... tetra.rotate(pgroup[i])
164
+ ...
165
+ >>> tetra.vertices
166
+ (x, w, z, y)
167
+ >>> sequentially = tetra.vertices
168
+
169
+ Apply the composite permutation:
170
+
171
+ >>> tetra.reset()
172
+ >>> tetra.rotate(p002)
173
+ >>> tetra.corners
174
+ (x, w, z, y)
175
+ >>> tetra.corners in all and tetra.corners == sequentially
176
+ True
177
+
178
+ Notes
179
+ =====
180
+
181
+ Defining permutation groups
182
+ ---------------------------
183
+
184
+ It is not necessary to enter any permutations, nor is necessary to
185
+ enter a complete set of transformations. In fact, for a polyhedron,
186
+ all configurations can be constructed from just two permutations.
187
+ For example, the orientations of a tetrahedron can be generated from
188
+ an axis passing through a vertex and face and another axis passing
189
+ through a different vertex or from an axis passing through the
190
+ midpoints of two edges opposite of each other.
191
+
192
+ For simplicity of presentation, consider a square --
193
+ not a cube -- with vertices 1, 2, 3, and 4:
194
+
195
+ 1-----2 We could think of axes of rotation being:
196
+ | | 1) through the face
197
+ | | 2) from midpoint 1-2 to 3-4 or 1-3 to 2-4
198
+ 3-----4 3) lines 1-4 or 2-3
199
+
200
+
201
+ To determine how to write the permutations, imagine 4 cameras,
202
+ one at each corner, labeled A-D:
203
+
204
+ A B A B
205
+ 1-----2 1-----3 vertex index:
206
+ | | | | 1 0
207
+ | | | | 2 1
208
+ 3-----4 2-----4 3 2
209
+ C D C D 4 3
210
+
211
+ original after rotation
212
+ along 1-4
213
+
214
+ A diagonal and a face axis will be chosen for the "permutation group"
215
+ from which any orientation can be constructed.
216
+
217
+ >>> pgroup = []
218
+
219
+ Imagine a clockwise rotation when viewing 1-4 from camera A. The new
220
+ orientation is (in camera-order): 1, 3, 2, 4 so the permutation is
221
+ given using the *indices* of the vertices as:
222
+
223
+ >>> pgroup.append(Permutation((0, 2, 1, 3)))
224
+
225
+ Now imagine rotating clockwise when looking down an axis entering the
226
+ center of the square as viewed. The new camera-order would be
227
+ 3, 1, 4, 2 so the permutation is (using indices):
228
+
229
+ >>> pgroup.append(Permutation((2, 0, 3, 1)))
230
+
231
+ The square can now be constructed:
232
+ ** use real-world labels for the vertices, entering them in
233
+ camera order
234
+ ** for the faces we use zero-based indices of the vertices
235
+ in *edge-order* as the face is traversed; neither the
236
+ direction nor the starting point matter -- the faces are
237
+ only used to define edges (if so desired).
238
+
239
+ >>> square = Polyhedron((1, 2, 3, 4), [(0, 1, 3, 2)], pgroup)
240
+
241
+ To rotate the square with a single permutation we can do:
242
+
243
+ >>> square.rotate(square.pgroup[0])
244
+ >>> square.corners
245
+ (1, 3, 2, 4)
246
+
247
+ To use more than one permutation (or to use one permutation more
248
+ than once) it is more convenient to use the make_perm method:
249
+
250
+ >>> p011 = square.pgroup.make_perm([0, 1, 1]) # diag flip + 2 rotations
251
+ >>> square.reset() # return to initial orientation
252
+ >>> square.rotate(p011)
253
+ >>> square.corners
254
+ (4, 2, 3, 1)
255
+
256
+ Thinking outside the box
257
+ ------------------------
258
+
259
+ Although the Polyhedron object has a direct physical meaning, it
260
+ actually has broader application. In the most general sense it is
261
+ just a decorated PermutationGroup, allowing one to connect the
262
+ permutations to something physical. For example, a Rubik's cube is
263
+ not a proper polyhedron, but the Polyhedron class can be used to
264
+ represent it in a way that helps to visualize the Rubik's cube.
265
+
266
+ >>> from sympy import flatten, unflatten, symbols
267
+ >>> from sympy.combinatorics import RubikGroup
268
+ >>> facelets = flatten([symbols(s+'1:5') for s in 'UFRBLD'])
269
+ >>> def show():
270
+ ... pairs = unflatten(r2.corners, 2)
271
+ ... print(pairs[::2])
272
+ ... print(pairs[1::2])
273
+ ...
274
+ >>> r2 = Polyhedron(facelets, pgroup=RubikGroup(2))
275
+ >>> show()
276
+ [(U1, U2), (F1, F2), (R1, R2), (B1, B2), (L1, L2), (D1, D2)]
277
+ [(U3, U4), (F3, F4), (R3, R4), (B3, B4), (L3, L4), (D3, D4)]
278
+ >>> r2.rotate(0) # cw rotation of F
279
+ >>> show()
280
+ [(U1, U2), (F3, F1), (U3, R2), (B1, B2), (L1, D1), (R3, R1)]
281
+ [(L4, L2), (F4, F2), (U4, R4), (B3, B4), (L3, D2), (D3, D4)]
282
+
283
+ Predefined Polyhedra
284
+ ====================
285
+
286
+ For convenience, the vertices and faces are defined for the following
287
+ standard solids along with a permutation group for transformations.
288
+ When the polyhedron is oriented as indicated below, the vertices in
289
+ a given horizontal plane are numbered in ccw direction, starting from
290
+ the vertex that will give the lowest indices in a given face. (In the
291
+ net of the vertices, indices preceded by "-" indicate replication of
292
+ the lhs index in the net.)
293
+
294
+ tetrahedron, tetrahedron_faces
295
+ ------------------------------
296
+
297
+ 4 vertices (vertex up) net:
298
+
299
+ 0 0-0
300
+ 1 2 3-1
301
+
302
+ 4 faces:
303
+
304
+ (0, 1, 2) (0, 2, 3) (0, 3, 1) (1, 2, 3)
305
+
306
+ cube, cube_faces
307
+ ----------------
308
+
309
+ 8 vertices (face up) net:
310
+
311
+ 0 1 2 3-0
312
+ 4 5 6 7-4
313
+
314
+ 6 faces:
315
+
316
+ (0, 1, 2, 3)
317
+ (0, 1, 5, 4) (1, 2, 6, 5) (2, 3, 7, 6) (0, 3, 7, 4)
318
+ (4, 5, 6, 7)
319
+
320
+ octahedron, octahedron_faces
321
+ ----------------------------
322
+
323
+ 6 vertices (vertex up) net:
324
+
325
+ 0 0 0-0
326
+ 1 2 3 4-1
327
+ 5 5 5-5
328
+
329
+ 8 faces:
330
+
331
+ (0, 1, 2) (0, 2, 3) (0, 3, 4) (0, 1, 4)
332
+ (1, 2, 5) (2, 3, 5) (3, 4, 5) (1, 4, 5)
333
+
334
+ dodecahedron, dodecahedron_faces
335
+ --------------------------------
336
+
337
+ 20 vertices (vertex up) net:
338
+
339
+ 0 1 2 3 4 -0
340
+ 5 6 7 8 9 -5
341
+ 14 10 11 12 13-14
342
+ 15 16 17 18 19-15
343
+
344
+ 12 faces:
345
+
346
+ (0, 1, 2, 3, 4) (0, 1, 6, 10, 5) (1, 2, 7, 11, 6)
347
+ (2, 3, 8, 12, 7) (3, 4, 9, 13, 8) (0, 4, 9, 14, 5)
348
+ (5, 10, 16, 15, 14) (6, 10, 16, 17, 11) (7, 11, 17, 18, 12)
349
+ (8, 12, 18, 19, 13) (9, 13, 19, 15, 14)(15, 16, 17, 18, 19)
350
+
351
+ icosahedron, icosahedron_faces
352
+ ------------------------------
353
+
354
+ 12 vertices (face up) net:
355
+
356
+ 0 0 0 0 -0
357
+ 1 2 3 4 5 -1
358
+ 6 7 8 9 10 -6
359
+ 11 11 11 11 -11
360
+
361
+ 20 faces:
362
+
363
+ (0, 1, 2) (0, 2, 3) (0, 3, 4)
364
+ (0, 4, 5) (0, 1, 5) (1, 2, 6)
365
+ (2, 3, 7) (3, 4, 8) (4, 5, 9)
366
+ (1, 5, 10) (2, 6, 7) (3, 7, 8)
367
+ (4, 8, 9) (5, 9, 10) (1, 6, 10)
368
+ (6, 7, 11) (7, 8, 11) (8, 9, 11)
369
+ (9, 10, 11) (6, 10, 11)
370
+
371
+ >>> from sympy.combinatorics.polyhedron import cube
372
+ >>> cube.edges
373
+ {(0, 1), (0, 3), (0, 4), (1, 2), (1, 5), (2, 3), (2, 6), (3, 7), (4, 5), (4, 7), (5, 6), (6, 7)}
374
+
375
+ If you want to use letters or other names for the corners you
376
+ can still use the pre-calculated faces:
377
+
378
+ >>> corners = list('abcdefgh')
379
+ >>> Polyhedron(corners, cube.faces).corners
380
+ (a, b, c, d, e, f, g, h)
381
+
382
+ References
383
+ ==========
384
+
385
+ .. [1] www.ocf.berkeley.edu/~wwu/articles/platonicsolids.pdf
386
+
387
+ """
388
+ faces = [minlex(f, directed=False, key=default_sort_key) for f in faces]
389
+ corners, faces, pgroup = args = \
390
+ [Tuple(*a) for a in (corners, faces, pgroup)]
391
+ obj = Basic.__new__(cls, *args)
392
+ obj._corners = tuple(corners) # in order given
393
+ obj._faces = FiniteSet(*faces)
394
+ if pgroup and pgroup[0].size != len(corners):
395
+ raise ValueError("Permutation size unequal to number of corners.")
396
+ # use the identity permutation if none are given
397
+ obj._pgroup = PermutationGroup(
398
+ pgroup or [Perm(range(len(corners)))] )
399
+ return obj
400
+
401
+ @property
402
+ def corners(self):
403
+ """
404
+ Get the corners of the Polyhedron.
405
+
406
+ The method ``vertices`` is an alias for ``corners``.
407
+
408
+ Examples
409
+ ========
410
+
411
+ >>> from sympy.combinatorics import Polyhedron
412
+ >>> from sympy.abc import a, b, c, d
413
+ >>> p = Polyhedron(list('abcd'))
414
+ >>> p.corners == p.vertices == (a, b, c, d)
415
+ True
416
+
417
+ See Also
418
+ ========
419
+
420
+ array_form, cyclic_form
421
+ """
422
+ return self._corners
423
+ vertices = corners
424
+
425
+ @property
426
+ def array_form(self):
427
+ """Return the indices of the corners.
428
+
429
+ The indices are given relative to the original position of corners.
430
+
431
+ Examples
432
+ ========
433
+
434
+ >>> from sympy.combinatorics.polyhedron import tetrahedron
435
+ >>> tetrahedron = tetrahedron.copy()
436
+ >>> tetrahedron.array_form
437
+ [0, 1, 2, 3]
438
+
439
+ >>> tetrahedron.rotate(0)
440
+ >>> tetrahedron.array_form
441
+ [0, 2, 3, 1]
442
+ >>> tetrahedron.pgroup[0].array_form
443
+ [0, 2, 3, 1]
444
+
445
+ See Also
446
+ ========
447
+
448
+ corners, cyclic_form
449
+ """
450
+ corners = list(self.args[0])
451
+ return [corners.index(c) for c in self.corners]
452
+
453
+ @property
454
+ def cyclic_form(self):
455
+ """Return the indices of the corners in cyclic notation.
456
+
457
+ The indices are given relative to the original position of corners.
458
+
459
+ See Also
460
+ ========
461
+
462
+ corners, array_form
463
+ """
464
+ return Perm._af_new(self.array_form).cyclic_form
465
+
466
+ @property
467
+ def size(self):
468
+ """
469
+ Get the number of corners of the Polyhedron.
470
+ """
471
+ return len(self._corners)
472
+
473
+ @property
474
+ def faces(self):
475
+ """
476
+ Get the faces of the Polyhedron.
477
+ """
478
+ return self._faces
479
+
480
+ @property
481
+ def pgroup(self):
482
+ """
483
+ Get the permutations of the Polyhedron.
484
+ """
485
+ return self._pgroup
486
+
487
+ @property
488
+ def edges(self):
489
+ """
490
+ Given the faces of the polyhedra we can get the edges.
491
+
492
+ Examples
493
+ ========
494
+
495
+ >>> from sympy.combinatorics import Polyhedron
496
+ >>> from sympy.abc import a, b, c
497
+ >>> corners = (a, b, c)
498
+ >>> faces = [(0, 1, 2)]
499
+ >>> Polyhedron(corners, faces).edges
500
+ {(0, 1), (0, 2), (1, 2)}
501
+
502
+ """
503
+ if self._edges is None:
504
+ output = set()
505
+ for face in self.faces:
506
+ for i in range(len(face)):
507
+ edge = tuple(sorted([face[i], face[i - 1]]))
508
+ output.add(edge)
509
+ self._edges = FiniteSet(*output)
510
+ return self._edges
511
+
512
+ def rotate(self, perm):
513
+ """
514
+ Apply a permutation to the polyhedron *in place*. The permutation
515
+ may be given as a Permutation instance or an integer indicating
516
+ which permutation from pgroup of the Polyhedron should be
517
+ applied.
518
+
519
+ This is an operation that is analogous to rotation about
520
+ an axis by a fixed increment.
521
+
522
+ Notes
523
+ =====
524
+
525
+ When a Permutation is applied, no check is done to see if that
526
+ is a valid permutation for the Polyhedron. For example, a cube
527
+ could be given a permutation which effectively swaps only 2
528
+ vertices. A valid permutation (that rotates the object in a
529
+ physical way) will be obtained if one only uses
530
+ permutations from the ``pgroup`` of the Polyhedron. On the other
531
+ hand, allowing arbitrary rotations (applications of permutations)
532
+ gives a way to follow named elements rather than indices since
533
+ Polyhedron allows vertices to be named while Permutation works
534
+ only with indices.
535
+
536
+ Examples
537
+ ========
538
+
539
+ >>> from sympy.combinatorics import Polyhedron, Permutation
540
+ >>> from sympy.combinatorics.polyhedron import cube
541
+ >>> cube = cube.copy()
542
+ >>> cube.corners
543
+ (0, 1, 2, 3, 4, 5, 6, 7)
544
+ >>> cube.rotate(0)
545
+ >>> cube.corners
546
+ (1, 2, 3, 0, 5, 6, 7, 4)
547
+
548
+ A non-physical "rotation" that is not prohibited by this method:
549
+
550
+ >>> cube.reset()
551
+ >>> cube.rotate(Permutation([[1, 2]], size=8))
552
+ >>> cube.corners
553
+ (0, 2, 1, 3, 4, 5, 6, 7)
554
+
555
+ Polyhedron can be used to follow elements of set that are
556
+ identified by letters instead of integers:
557
+
558
+ >>> shadow = h5 = Polyhedron(list('abcde'))
559
+ >>> p = Permutation([3, 0, 1, 2, 4])
560
+ >>> h5.rotate(p)
561
+ >>> h5.corners
562
+ (d, a, b, c, e)
563
+ >>> _ == shadow.corners
564
+ True
565
+ >>> copy = h5.copy()
566
+ >>> h5.rotate(p)
567
+ >>> h5.corners == copy.corners
568
+ False
569
+ """
570
+ if not isinstance(perm, Perm):
571
+ perm = self.pgroup[perm]
572
+ # and we know it's valid
573
+ else:
574
+ if perm.size != self.size:
575
+ raise ValueError('Polyhedron and Permutation sizes differ.')
576
+ a = perm.array_form
577
+ corners = [self.corners[a[i]] for i in range(len(self.corners))]
578
+ self._corners = tuple(corners)
579
+
580
+ def reset(self):
581
+ """Return corners to their original positions.
582
+
583
+ Examples
584
+ ========
585
+
586
+ >>> from sympy.combinatorics.polyhedron import tetrahedron as T
587
+ >>> T = T.copy()
588
+ >>> T.corners
589
+ (0, 1, 2, 3)
590
+ >>> T.rotate(0)
591
+ >>> T.corners
592
+ (0, 2, 3, 1)
593
+ >>> T.reset()
594
+ >>> T.corners
595
+ (0, 1, 2, 3)
596
+ """
597
+ self._corners = self.args[0]
598
+
599
+
600
+ def _pgroup_calcs():
601
+ """Return the permutation groups for each of the polyhedra and the face
602
+ definitions: tetrahedron, cube, octahedron, dodecahedron, icosahedron,
603
+ tetrahedron_faces, cube_faces, octahedron_faces, dodecahedron_faces,
604
+ icosahedron_faces
605
+
606
+ Explanation
607
+ ===========
608
+
609
+ (This author did not find and did not know of a better way to do it though
610
+ there likely is such a way.)
611
+
612
+ Although only 2 permutations are needed for a polyhedron in order to
613
+ generate all the possible orientations, a group of permutations is
614
+ provided instead. A set of permutations is called a "group" if::
615
+
616
+ a*b = c (for any pair of permutations in the group, a and b, their
617
+ product, c, is in the group)
618
+
619
+ a*(b*c) = (a*b)*c (for any 3 permutations in the group associativity holds)
620
+
621
+ there is an identity permutation, I, such that I*a = a*I for all elements
622
+ in the group
623
+
624
+ a*b = I (the inverse of each permutation is also in the group)
625
+
626
+ None of the polyhedron groups defined follow these definitions of a group.
627
+ Instead, they are selected to contain those permutations whose powers
628
+ alone will construct all orientations of the polyhedron, i.e. for
629
+ permutations ``a``, ``b``, etc... in the group, ``a, a**2, ..., a**o_a``,
630
+ ``b, b**2, ..., b**o_b``, etc... (where ``o_i`` is the order of
631
+ permutation ``i``) generate all permutations of the polyhedron instead of
632
+ mixed products like ``a*b``, ``a*b**2``, etc....
633
+
634
+ Note that for a polyhedron with n vertices, the valid permutations of the
635
+ vertices exclude those that do not maintain its faces. e.g. the
636
+ permutation BCDE of a square's four corners, ABCD, is a valid
637
+ permutation while CBDE is not (because this would twist the square).
638
+
639
+ Examples
640
+ ========
641
+
642
+ The is_group checks for: closure, the presence of the Identity permutation,
643
+ and the presence of the inverse for each of the elements in the group. This
644
+ confirms that none of the polyhedra are true groups:
645
+
646
+ >>> from sympy.combinatorics.polyhedron import (
647
+ ... tetrahedron, cube, octahedron, dodecahedron, icosahedron)
648
+ ...
649
+ >>> polyhedra = (tetrahedron, cube, octahedron, dodecahedron, icosahedron)
650
+ >>> [h.pgroup.is_group for h in polyhedra]
651
+ ...
652
+ [True, True, True, True, True]
653
+
654
+ Although tests in polyhedron's test suite check that powers of the
655
+ permutations in the groups generate all permutations of the vertices
656
+ of the polyhedron, here we also demonstrate the powers of the given
657
+ permutations create a complete group for the tetrahedron:
658
+
659
+ >>> from sympy.combinatorics import Permutation, PermutationGroup
660
+ >>> for h in polyhedra[:1]:
661
+ ... G = h.pgroup
662
+ ... perms = set()
663
+ ... for g in G:
664
+ ... for e in range(g.order()):
665
+ ... p = tuple((g**e).array_form)
666
+ ... perms.add(p)
667
+ ...
668
+ ... perms = [Permutation(p) for p in perms]
669
+ ... assert PermutationGroup(perms).is_group
670
+
671
+ In addition to doing the above, the tests in the suite confirm that the
672
+ faces are all present after the application of each permutation.
673
+
674
+ References
675
+ ==========
676
+
677
+ .. [1] https://dogschool.tripod.com/trianglegroup.html
678
+
679
+ """
680
+ def _pgroup_of_double(polyh, ordered_faces, pgroup):
681
+ n = len(ordered_faces[0])
682
+ # the vertices of the double which sits inside a give polyhedron
683
+ # can be found by tracking the faces of the outer polyhedron.
684
+ # A map between face and the vertex of the double is made so that
685
+ # after rotation the position of the vertices can be located
686
+ fmap = dict(zip(ordered_faces,
687
+ range(len(ordered_faces))))
688
+ flat_faces = flatten(ordered_faces)
689
+ new_pgroup = []
690
+ for p in pgroup:
691
+ h = polyh.copy()
692
+ h.rotate(p)
693
+ c = h.corners
694
+ # reorder corners in the order they should appear when
695
+ # enumerating the faces
696
+ reorder = unflatten([c[j] for j in flat_faces], n)
697
+ # make them canonical
698
+ reorder = [tuple(map(as_int,
699
+ minlex(f, directed=False)))
700
+ for f in reorder]
701
+ # map face to vertex: the resulting list of vertices are the
702
+ # permutation that we seek for the double
703
+ new_pgroup.append(Perm([fmap[f] for f in reorder]))
704
+ return new_pgroup
705
+
706
+ tetrahedron_faces = [
707
+ (0, 1, 2), (0, 2, 3), (0, 3, 1), # upper 3
708
+ (1, 2, 3), # bottom
709
+ ]
710
+
711
+ # cw from top
712
+ #
713
+ _t_pgroup = [
714
+ Perm([[1, 2, 3], [0]]), # cw from top
715
+ Perm([[0, 1, 2], [3]]), # cw from front face
716
+ Perm([[0, 3, 2], [1]]), # cw from back right face
717
+ Perm([[0, 3, 1], [2]]), # cw from back left face
718
+ Perm([[0, 1], [2, 3]]), # through front left edge
719
+ Perm([[0, 2], [1, 3]]), # through front right edge
720
+ Perm([[0, 3], [1, 2]]), # through back edge
721
+ ]
722
+
723
+ tetrahedron = Polyhedron(
724
+ range(4),
725
+ tetrahedron_faces,
726
+ _t_pgroup)
727
+
728
+ cube_faces = [
729
+ (0, 1, 2, 3), # upper
730
+ (0, 1, 5, 4), (1, 2, 6, 5), (2, 3, 7, 6), (0, 3, 7, 4), # middle 4
731
+ (4, 5, 6, 7), # lower
732
+ ]
733
+
734
+ # U, D, F, B, L, R = up, down, front, back, left, right
735
+ _c_pgroup = [Perm(p) for p in
736
+ [
737
+ [1, 2, 3, 0, 5, 6, 7, 4], # cw from top, U
738
+ [4, 0, 3, 7, 5, 1, 2, 6], # cw from F face
739
+ [4, 5, 1, 0, 7, 6, 2, 3], # cw from R face
740
+
741
+ [1, 0, 4, 5, 2, 3, 7, 6], # cw through UF edge
742
+ [6, 2, 1, 5, 7, 3, 0, 4], # cw through UR edge
743
+ [6, 7, 3, 2, 5, 4, 0, 1], # cw through UB edge
744
+ [3, 7, 4, 0, 2, 6, 5, 1], # cw through UL edge
745
+ [4, 7, 6, 5, 0, 3, 2, 1], # cw through FL edge
746
+ [6, 5, 4, 7, 2, 1, 0, 3], # cw through FR edge
747
+
748
+ [0, 3, 7, 4, 1, 2, 6, 5], # cw through UFL vertex
749
+ [5, 1, 0, 4, 6, 2, 3, 7], # cw through UFR vertex
750
+ [5, 6, 2, 1, 4, 7, 3, 0], # cw through UBR vertex
751
+ [7, 4, 0, 3, 6, 5, 1, 2], # cw through UBL
752
+ ]]
753
+
754
+ cube = Polyhedron(
755
+ range(8),
756
+ cube_faces,
757
+ _c_pgroup)
758
+
759
+ octahedron_faces = [
760
+ (0, 1, 2), (0, 2, 3), (0, 3, 4), (0, 1, 4), # top 4
761
+ (1, 2, 5), (2, 3, 5), (3, 4, 5), (1, 4, 5), # bottom 4
762
+ ]
763
+
764
+ octahedron = Polyhedron(
765
+ range(6),
766
+ octahedron_faces,
767
+ _pgroup_of_double(cube, cube_faces, _c_pgroup))
768
+
769
+ dodecahedron_faces = [
770
+ (0, 1, 2, 3, 4), # top
771
+ (0, 1, 6, 10, 5), (1, 2, 7, 11, 6), (2, 3, 8, 12, 7), # upper 5
772
+ (3, 4, 9, 13, 8), (0, 4, 9, 14, 5),
773
+ (5, 10, 16, 15, 14), (6, 10, 16, 17, 11), (7, 11, 17, 18,
774
+ 12), # lower 5
775
+ (8, 12, 18, 19, 13), (9, 13, 19, 15, 14),
776
+ (15, 16, 17, 18, 19) # bottom
777
+ ]
778
+
779
+ def _string_to_perm(s):
780
+ rv = [Perm(range(20))]
781
+ p = None
782
+ for si in s:
783
+ if si not in '01':
784
+ count = int(si) - 1
785
+ else:
786
+ count = 1
787
+ if si == '0':
788
+ p = _f0
789
+ elif si == '1':
790
+ p = _f1
791
+ rv.extend([p]*count)
792
+ return Perm.rmul(*rv)
793
+
794
+ # top face cw
795
+ _f0 = Perm([
796
+ 1, 2, 3, 4, 0, 6, 7, 8, 9, 5, 11,
797
+ 12, 13, 14, 10, 16, 17, 18, 19, 15])
798
+ # front face cw
799
+ _f1 = Perm([
800
+ 5, 0, 4, 9, 14, 10, 1, 3, 13, 15,
801
+ 6, 2, 8, 19, 16, 17, 11, 7, 12, 18])
802
+ # the strings below, like 0104 are shorthand for F0*F1*F0**4 and are
803
+ # the remaining 4 face rotations, 15 edge permutations, and the
804
+ # 10 vertex rotations.
805
+ _dodeca_pgroup = [_f0, _f1] + [_string_to_perm(s) for s in '''
806
+ 0104 140 014 0410
807
+ 010 1403 03104 04103 102
808
+ 120 1304 01303 021302 03130
809
+ 0412041 041204103 04120410 041204104 041204102
810
+ 10 01 1402 0140 04102 0412 1204 1302 0130 03120'''.strip().split()]
811
+
812
+ dodecahedron = Polyhedron(
813
+ range(20),
814
+ dodecahedron_faces,
815
+ _dodeca_pgroup)
816
+
817
+ icosahedron_faces = [
818
+ (0, 1, 2), (0, 2, 3), (0, 3, 4), (0, 4, 5), (0, 1, 5),
819
+ (1, 6, 7), (1, 2, 7), (2, 7, 8), (2, 3, 8), (3, 8, 9),
820
+ (3, 4, 9), (4, 9, 10), (4, 5, 10), (5, 6, 10), (1, 5, 6),
821
+ (6, 7, 11), (7, 8, 11), (8, 9, 11), (9, 10, 11), (6, 10, 11)]
822
+
823
+ icosahedron = Polyhedron(
824
+ range(12),
825
+ icosahedron_faces,
826
+ _pgroup_of_double(
827
+ dodecahedron, dodecahedron_faces, _dodeca_pgroup))
828
+
829
+ return (tetrahedron, cube, octahedron, dodecahedron, icosahedron,
830
+ tetrahedron_faces, cube_faces, octahedron_faces,
831
+ dodecahedron_faces, icosahedron_faces)
832
+
833
+ # -----------------------------------------------------------------------
834
+ # Standard Polyhedron groups
835
+ #
836
+ # These are generated using _pgroup_calcs() above. However to save
837
+ # import time we encode them explicitly here.
838
+ # -----------------------------------------------------------------------
839
+
840
+ tetrahedron = Polyhedron(
841
+ Tuple(0, 1, 2, 3),
842
+ Tuple(
843
+ Tuple(0, 1, 2),
844
+ Tuple(0, 2, 3),
845
+ Tuple(0, 1, 3),
846
+ Tuple(1, 2, 3)),
847
+ Tuple(
848
+ Perm(1, 2, 3),
849
+ Perm(3)(0, 1, 2),
850
+ Perm(0, 3, 2),
851
+ Perm(0, 3, 1),
852
+ Perm(0, 1)(2, 3),
853
+ Perm(0, 2)(1, 3),
854
+ Perm(0, 3)(1, 2)
855
+ ))
856
+
857
+ cube = Polyhedron(
858
+ Tuple(0, 1, 2, 3, 4, 5, 6, 7),
859
+ Tuple(
860
+ Tuple(0, 1, 2, 3),
861
+ Tuple(0, 1, 5, 4),
862
+ Tuple(1, 2, 6, 5),
863
+ Tuple(2, 3, 7, 6),
864
+ Tuple(0, 3, 7, 4),
865
+ Tuple(4, 5, 6, 7)),
866
+ Tuple(
867
+ Perm(0, 1, 2, 3)(4, 5, 6, 7),
868
+ Perm(0, 4, 5, 1)(2, 3, 7, 6),
869
+ Perm(0, 4, 7, 3)(1, 5, 6, 2),
870
+ Perm(0, 1)(2, 4)(3, 5)(6, 7),
871
+ Perm(0, 6)(1, 2)(3, 5)(4, 7),
872
+ Perm(0, 6)(1, 7)(2, 3)(4, 5),
873
+ Perm(0, 3)(1, 7)(2, 4)(5, 6),
874
+ Perm(0, 4)(1, 7)(2, 6)(3, 5),
875
+ Perm(0, 6)(1, 5)(2, 4)(3, 7),
876
+ Perm(1, 3, 4)(2, 7, 5),
877
+ Perm(7)(0, 5, 2)(3, 4, 6),
878
+ Perm(0, 5, 7)(1, 6, 3),
879
+ Perm(0, 7, 2)(1, 4, 6)))
880
+
881
+ octahedron = Polyhedron(
882
+ Tuple(0, 1, 2, 3, 4, 5),
883
+ Tuple(
884
+ Tuple(0, 1, 2),
885
+ Tuple(0, 2, 3),
886
+ Tuple(0, 3, 4),
887
+ Tuple(0, 1, 4),
888
+ Tuple(1, 2, 5),
889
+ Tuple(2, 3, 5),
890
+ Tuple(3, 4, 5),
891
+ Tuple(1, 4, 5)),
892
+ Tuple(
893
+ Perm(5)(1, 2, 3, 4),
894
+ Perm(0, 4, 5, 2),
895
+ Perm(0, 1, 5, 3),
896
+ Perm(0, 1)(2, 4)(3, 5),
897
+ Perm(0, 2)(1, 3)(4, 5),
898
+ Perm(0, 3)(1, 5)(2, 4),
899
+ Perm(0, 4)(1, 3)(2, 5),
900
+ Perm(0, 5)(1, 4)(2, 3),
901
+ Perm(0, 5)(1, 2)(3, 4),
902
+ Perm(0, 4, 1)(2, 3, 5),
903
+ Perm(0, 1, 2)(3, 4, 5),
904
+ Perm(0, 2, 3)(1, 5, 4),
905
+ Perm(0, 4, 3)(1, 5, 2)))
906
+
907
+ dodecahedron = Polyhedron(
908
+ Tuple(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19),
909
+ Tuple(
910
+ Tuple(0, 1, 2, 3, 4),
911
+ Tuple(0, 1, 6, 10, 5),
912
+ Tuple(1, 2, 7, 11, 6),
913
+ Tuple(2, 3, 8, 12, 7),
914
+ Tuple(3, 4, 9, 13, 8),
915
+ Tuple(0, 4, 9, 14, 5),
916
+ Tuple(5, 10, 16, 15, 14),
917
+ Tuple(6, 10, 16, 17, 11),
918
+ Tuple(7, 11, 17, 18, 12),
919
+ Tuple(8, 12, 18, 19, 13),
920
+ Tuple(9, 13, 19, 15, 14),
921
+ Tuple(15, 16, 17, 18, 19)),
922
+ Tuple(
923
+ Perm(0, 1, 2, 3, 4)(5, 6, 7, 8, 9)(10, 11, 12, 13, 14)(15, 16, 17, 18, 19),
924
+ Perm(0, 5, 10, 6, 1)(2, 4, 14, 16, 11)(3, 9, 15, 17, 7)(8, 13, 19, 18, 12),
925
+ Perm(0, 10, 17, 12, 3)(1, 6, 11, 7, 2)(4, 5, 16, 18, 8)(9, 14, 15, 19, 13),
926
+ Perm(0, 6, 17, 19, 9)(1, 11, 18, 13, 4)(2, 7, 12, 8, 3)(5, 10, 16, 15, 14),
927
+ Perm(0, 2, 12, 19, 14)(1, 7, 18, 15, 5)(3, 8, 13, 9, 4)(6, 11, 17, 16, 10),
928
+ Perm(0, 4, 9, 14, 5)(1, 3, 13, 15, 10)(2, 8, 19, 16, 6)(7, 12, 18, 17, 11),
929
+ Perm(0, 1)(2, 5)(3, 10)(4, 6)(7, 14)(8, 16)(9, 11)(12, 15)(13, 17)(18, 19),
930
+ Perm(0, 7)(1, 2)(3, 6)(4, 11)(5, 12)(8, 10)(9, 17)(13, 16)(14, 18)(15, 19),
931
+ Perm(0, 12)(1, 8)(2, 3)(4, 7)(5, 18)(6, 13)(9, 11)(10, 19)(14, 17)(15, 16),
932
+ Perm(0, 8)(1, 13)(2, 9)(3, 4)(5, 12)(6, 19)(7, 14)(10, 18)(11, 15)(16, 17),
933
+ Perm(0, 4)(1, 9)(2, 14)(3, 5)(6, 13)(7, 15)(8, 10)(11, 19)(12, 16)(17, 18),
934
+ Perm(0, 5)(1, 14)(2, 15)(3, 16)(4, 10)(6, 9)(7, 19)(8, 17)(11, 13)(12, 18),
935
+ Perm(0, 11)(1, 6)(2, 10)(3, 16)(4, 17)(5, 7)(8, 15)(9, 18)(12, 14)(13, 19),
936
+ Perm(0, 18)(1, 12)(2, 7)(3, 11)(4, 17)(5, 19)(6, 8)(9, 16)(10, 13)(14, 15),
937
+ Perm(0, 18)(1, 19)(2, 13)(3, 8)(4, 12)(5, 17)(6, 15)(7, 9)(10, 16)(11, 14),
938
+ Perm(0, 13)(1, 19)(2, 15)(3, 14)(4, 9)(5, 8)(6, 18)(7, 16)(10, 12)(11, 17),
939
+ Perm(0, 16)(1, 15)(2, 19)(3, 18)(4, 17)(5, 10)(6, 14)(7, 13)(8, 12)(9, 11),
940
+ Perm(0, 18)(1, 17)(2, 16)(3, 15)(4, 19)(5, 12)(6, 11)(7, 10)(8, 14)(9, 13),
941
+ Perm(0, 15)(1, 19)(2, 18)(3, 17)(4, 16)(5, 14)(6, 13)(7, 12)(8, 11)(9, 10),
942
+ Perm(0, 17)(1, 16)(2, 15)(3, 19)(4, 18)(5, 11)(6, 10)(7, 14)(8, 13)(9, 12),
943
+ Perm(0, 19)(1, 18)(2, 17)(3, 16)(4, 15)(5, 13)(6, 12)(7, 11)(8, 10)(9, 14),
944
+ Perm(1, 4, 5)(2, 9, 10)(3, 14, 6)(7, 13, 16)(8, 15, 11)(12, 19, 17),
945
+ Perm(19)(0, 6, 2)(3, 5, 11)(4, 10, 7)(8, 14, 17)(9, 16, 12)(13, 15, 18),
946
+ Perm(0, 11, 8)(1, 7, 3)(4, 6, 12)(5, 17, 13)(9, 10, 18)(14, 16, 19),
947
+ Perm(0, 7, 13)(1, 12, 9)(2, 8, 4)(5, 11, 19)(6, 18, 14)(10, 17, 15),
948
+ Perm(0, 3, 9)(1, 8, 14)(2, 13, 5)(6, 12, 15)(7, 19, 10)(11, 18, 16),
949
+ Perm(0, 14, 10)(1, 9, 16)(2, 13, 17)(3, 19, 11)(4, 15, 6)(7, 8, 18),
950
+ Perm(0, 16, 7)(1, 10, 11)(2, 5, 17)(3, 14, 18)(4, 15, 12)(8, 9, 19),
951
+ Perm(0, 16, 13)(1, 17, 8)(2, 11, 12)(3, 6, 18)(4, 10, 19)(5, 15, 9),
952
+ Perm(0, 11, 15)(1, 17, 14)(2, 18, 9)(3, 12, 13)(4, 7, 19)(5, 6, 16),
953
+ Perm(0, 8, 15)(1, 12, 16)(2, 18, 10)(3, 19, 5)(4, 13, 14)(6, 7, 17)))
954
+
955
+ icosahedron = Polyhedron(
956
+ Tuple(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11),
957
+ Tuple(
958
+ Tuple(0, 1, 2),
959
+ Tuple(0, 2, 3),
960
+ Tuple(0, 3, 4),
961
+ Tuple(0, 4, 5),
962
+ Tuple(0, 1, 5),
963
+ Tuple(1, 6, 7),
964
+ Tuple(1, 2, 7),
965
+ Tuple(2, 7, 8),
966
+ Tuple(2, 3, 8),
967
+ Tuple(3, 8, 9),
968
+ Tuple(3, 4, 9),
969
+ Tuple(4, 9, 10),
970
+ Tuple(4, 5, 10),
971
+ Tuple(5, 6, 10),
972
+ Tuple(1, 5, 6),
973
+ Tuple(6, 7, 11),
974
+ Tuple(7, 8, 11),
975
+ Tuple(8, 9, 11),
976
+ Tuple(9, 10, 11),
977
+ Tuple(6, 10, 11)),
978
+ Tuple(
979
+ Perm(11)(1, 2, 3, 4, 5)(6, 7, 8, 9, 10),
980
+ Perm(0, 5, 6, 7, 2)(3, 4, 10, 11, 8),
981
+ Perm(0, 1, 7, 8, 3)(4, 5, 6, 11, 9),
982
+ Perm(0, 2, 8, 9, 4)(1, 7, 11, 10, 5),
983
+ Perm(0, 3, 9, 10, 5)(1, 2, 8, 11, 6),
984
+ Perm(0, 4, 10, 6, 1)(2, 3, 9, 11, 7),
985
+ Perm(0, 1)(2, 5)(3, 6)(4, 7)(8, 10)(9, 11),
986
+ Perm(0, 2)(1, 3)(4, 7)(5, 8)(6, 9)(10, 11),
987
+ Perm(0, 3)(1, 9)(2, 4)(5, 8)(6, 11)(7, 10),
988
+ Perm(0, 4)(1, 9)(2, 10)(3, 5)(6, 8)(7, 11),
989
+ Perm(0, 5)(1, 4)(2, 10)(3, 6)(7, 9)(8, 11),
990
+ Perm(0, 6)(1, 5)(2, 10)(3, 11)(4, 7)(8, 9),
991
+ Perm(0, 7)(1, 2)(3, 6)(4, 11)(5, 8)(9, 10),
992
+ Perm(0, 8)(1, 9)(2, 3)(4, 7)(5, 11)(6, 10),
993
+ Perm(0, 9)(1, 11)(2, 10)(3, 4)(5, 8)(6, 7),
994
+ Perm(0, 10)(1, 9)(2, 11)(3, 6)(4, 5)(7, 8),
995
+ Perm(0, 11)(1, 6)(2, 10)(3, 9)(4, 8)(5, 7),
996
+ Perm(0, 11)(1, 8)(2, 7)(3, 6)(4, 10)(5, 9),
997
+ Perm(0, 11)(1, 10)(2, 9)(3, 8)(4, 7)(5, 6),
998
+ Perm(0, 11)(1, 7)(2, 6)(3, 10)(4, 9)(5, 8),
999
+ Perm(0, 11)(1, 9)(2, 8)(3, 7)(4, 6)(5, 10),
1000
+ Perm(0, 5, 1)(2, 4, 6)(3, 10, 7)(8, 9, 11),
1001
+ Perm(0, 1, 2)(3, 5, 7)(4, 6, 8)(9, 10, 11),
1002
+ Perm(0, 2, 3)(1, 8, 4)(5, 7, 9)(6, 11, 10),
1003
+ Perm(0, 3, 4)(1, 8, 10)(2, 9, 5)(6, 7, 11),
1004
+ Perm(0, 4, 5)(1, 3, 10)(2, 9, 6)(7, 8, 11),
1005
+ Perm(0, 10, 7)(1, 5, 6)(2, 4, 11)(3, 9, 8),
1006
+ Perm(0, 6, 8)(1, 7, 2)(3, 5, 11)(4, 10, 9),
1007
+ Perm(0, 7, 9)(1, 11, 4)(2, 8, 3)(5, 6, 10),
1008
+ Perm(0, 8, 10)(1, 7, 6)(2, 11, 5)(3, 9, 4),
1009
+ Perm(0, 9, 6)(1, 3, 11)(2, 8, 7)(4, 10, 5)))
1010
+
1011
+ tetrahedron_faces = [tuple(arg) for arg in tetrahedron.faces]
1012
+
1013
+ cube_faces = [tuple(arg) for arg in cube.faces]
1014
+
1015
+ octahedron_faces = [tuple(arg) for arg in octahedron.faces]
1016
+
1017
+ dodecahedron_faces = [tuple(arg) for arg in dodecahedron.faces]
1018
+
1019
+ icosahedron_faces = [tuple(arg) for arg in icosahedron.faces]