Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .venv/lib/python3.13/site-packages/sympy/algebras/__init__.py +3 -0
- .venv/lib/python3.13/site-packages/sympy/algebras/quaternion.py +1666 -0
- .venv/lib/python3.13/site-packages/sympy/assumptions/__init__.py +18 -0
- .venv/lib/python3.13/site-packages/sympy/assumptions/ask.py +651 -0
- .venv/lib/python3.13/site-packages/sympy/assumptions/ask_generated.py +352 -0
- .venv/lib/python3.13/site-packages/sympy/assumptions/assume.py +485 -0
- .venv/lib/python3.13/site-packages/sympy/assumptions/cnf.py +445 -0
- .venv/lib/python3.13/site-packages/sympy/assumptions/facts.py +270 -0
- .venv/lib/python3.13/site-packages/sympy/assumptions/lra_satask.py +286 -0
- .venv/lib/python3.13/site-packages/sympy/assumptions/refine.py +405 -0
- .venv/lib/python3.13/site-packages/sympy/assumptions/satask.py +369 -0
- .venv/lib/python3.13/site-packages/sympy/assumptions/sathandlers.py +322 -0
- .venv/lib/python3.13/site-packages/sympy/assumptions/wrapper.py +164 -0
- .venv/lib/python3.13/site-packages/sympy/benchmarks/__init__.py +0 -0
- .venv/lib/python3.13/site-packages/sympy/benchmarks/bench_discrete_log.py +83 -0
- .venv/lib/python3.13/site-packages/sympy/benchmarks/bench_meijerint.py +261 -0
- .venv/lib/python3.13/site-packages/sympy/benchmarks/bench_symbench.py +134 -0
- .venv/lib/python3.13/site-packages/sympy/codegen/__init__.py +24 -0
- .venv/lib/python3.13/site-packages/sympy/codegen/abstract_nodes.py +18 -0
- .venv/lib/python3.13/site-packages/sympy/codegen/algorithms.py +180 -0
- .venv/lib/python3.13/site-packages/sympy/codegen/approximations.py +187 -0
- .venv/lib/python3.13/site-packages/sympy/codegen/ast.py +1906 -0
- .venv/lib/python3.13/site-packages/sympy/codegen/cfunctions.py +558 -0
- .venv/lib/python3.13/site-packages/sympy/codegen/cnodes.py +156 -0
- .venv/lib/python3.13/site-packages/sympy/codegen/cutils.py +8 -0
- .venv/lib/python3.13/site-packages/sympy/codegen/cxxnodes.py +14 -0
- .venv/lib/python3.13/site-packages/sympy/codegen/fnodes.py +658 -0
- .venv/lib/python3.13/site-packages/sympy/codegen/futils.py +40 -0
- .venv/lib/python3.13/site-packages/sympy/codegen/matrix_nodes.py +71 -0
- .venv/lib/python3.13/site-packages/sympy/codegen/numpy_nodes.py +177 -0
- .venv/lib/python3.13/site-packages/sympy/codegen/pynodes.py +11 -0
- .venv/lib/python3.13/site-packages/sympy/codegen/pyutils.py +24 -0
- .venv/lib/python3.13/site-packages/sympy/codegen/rewriting.py +357 -0
- .venv/lib/python3.13/site-packages/sympy/codegen/scipy_nodes.py +79 -0
- .venv/lib/python3.13/site-packages/sympy/combinatorics/__init__.py +43 -0
- .venv/lib/python3.13/site-packages/sympy/combinatorics/coset_table.py +1259 -0
- .venv/lib/python3.13/site-packages/sympy/combinatorics/fp_groups.py +1352 -0
- .venv/lib/python3.13/site-packages/sympy/combinatorics/free_groups.py +1360 -0
- .venv/lib/python3.13/site-packages/sympy/combinatorics/galois.py +611 -0
- .venv/lib/python3.13/site-packages/sympy/combinatorics/generators.py +301 -0
- .venv/lib/python3.13/site-packages/sympy/combinatorics/graycode.py +430 -0
- .venv/lib/python3.13/site-packages/sympy/combinatorics/group_constructs.py +61 -0
- .venv/lib/python3.13/site-packages/sympy/combinatorics/group_numbers.py +294 -0
- .venv/lib/python3.13/site-packages/sympy/combinatorics/homomorphisms.py +549 -0
- .venv/lib/python3.13/site-packages/sympy/combinatorics/named_groups.py +332 -0
- .venv/lib/python3.13/site-packages/sympy/combinatorics/partitions.py +745 -0
- .venv/lib/python3.13/site-packages/sympy/combinatorics/pc_groups.py +710 -0
- .venv/lib/python3.13/site-packages/sympy/combinatorics/perm_groups.py +0 -0
- .venv/lib/python3.13/site-packages/sympy/combinatorics/permutations.py +3114 -0
- .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]
|