Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .gitattributes +2 -0
- .venv/lib/python3.11/site-packages/sympy/matrices/__pycache__/__init__.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/sympy/matrices/__pycache__/decompositions.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/sympy/matrices/__pycache__/determinant.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/sympy/matrices/__pycache__/eigen.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/sympy/matrices/__pycache__/exceptions.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/sympy/matrices/__pycache__/graph.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/sympy/matrices/__pycache__/immutable.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/sympy/matrices/__pycache__/inverse.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/sympy/matrices/__pycache__/matrices.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/sympy/matrices/__pycache__/reductions.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/sympy/matrices/__pycache__/repmatrix.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/sympy/matrices/__pycache__/solvers.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/sympy/matrices/__pycache__/sparse.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/sympy/matrices/__pycache__/sparsetools.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/sympy/matrices/__pycache__/subspaces.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/sympy/matrices/__pycache__/utilities.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/sympy/matrices/common.py +3263 -0
- .venv/lib/python3.11/site-packages/sympy/matrices/determinant.py +1021 -0
- .venv/lib/python3.11/site-packages/sympy/matrices/eigen.py +1346 -0
- .venv/lib/python3.11/site-packages/sympy/matrices/expressions/__init__.py +62 -0
- .venv/lib/python3.11/site-packages/sympy/matrices/expressions/__pycache__/__init__.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/sympy/matrices/expressions/__pycache__/_shape.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/sympy/matrices/expressions/__pycache__/adjoint.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/sympy/matrices/expressions/__pycache__/applyfunc.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/sympy/matrices/expressions/__pycache__/blockmatrix.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/sympy/matrices/expressions/__pycache__/companion.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/sympy/matrices/expressions/__pycache__/diagonal.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/sympy/matrices/expressions/__pycache__/dotproduct.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/sympy/matrices/expressions/__pycache__/fourier.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/sympy/matrices/expressions/__pycache__/funcmatrix.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/sympy/matrices/expressions/__pycache__/inverse.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/sympy/matrices/expressions/__pycache__/kronecker.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/sympy/matrices/expressions/__pycache__/matadd.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/sympy/matrices/expressions/__pycache__/matpow.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/sympy/matrices/expressions/__pycache__/special.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/sympy/matrices/expressions/adjoint.py +60 -0
- .venv/lib/python3.11/site-packages/sympy/matrices/expressions/applyfunc.py +204 -0
- .venv/lib/python3.11/site-packages/sympy/matrices/expressions/blockmatrix.py +980 -0
- .venv/lib/python3.11/site-packages/sympy/matrices/expressions/dotproduct.py +55 -0
- .venv/lib/python3.11/site-packages/sympy/matrices/expressions/factorizations.py +62 -0
- .venv/lib/python3.11/site-packages/sympy/matrices/expressions/funcmatrix.py +118 -0
- .venv/lib/python3.11/site-packages/sympy/matrices/expressions/inverse.py +112 -0
- .venv/lib/python3.11/site-packages/sympy/matrices/expressions/kronecker.py +434 -0
- .venv/lib/python3.11/site-packages/sympy/matrices/expressions/matexpr.py +888 -0
- .venv/lib/python3.11/site-packages/sympy/matrices/expressions/matpow.py +150 -0
- .venv/lib/python3.11/site-packages/sympy/matrices/expressions/slice.py +114 -0
- .venv/lib/python3.11/site-packages/sympy/matrices/expressions/tests/__pycache__/test_applyfunc.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/sympy/matrices/expressions/tests/__pycache__/test_derivatives.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/sympy/matrices/expressions/tests/__pycache__/test_dotproduct.cpython-311.pyc +0 -0
.gitattributes
CHANGED
@@ -442,3 +442,5 @@ tuning-competition-baseline/.venv/lib/python3.11/site-packages/nvidia/cudnn/lib/
|
|
442 |
.venv/lib/python3.11/site-packages/sympy/simplify/__pycache__/hyperexpand.cpython-311.pyc filter=lfs diff=lfs merge=lfs -text
|
443 |
.venv/lib/python3.11/site-packages/sympy/crypto/__pycache__/crypto.cpython-311.pyc filter=lfs diff=lfs merge=lfs -text
|
444 |
.venv/lib/python3.11/site-packages/sympy/simplify/tests/__pycache__/test_simplify.cpython-311.pyc filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
442 |
.venv/lib/python3.11/site-packages/sympy/simplify/__pycache__/hyperexpand.cpython-311.pyc filter=lfs diff=lfs merge=lfs -text
|
443 |
.venv/lib/python3.11/site-packages/sympy/crypto/__pycache__/crypto.cpython-311.pyc filter=lfs diff=lfs merge=lfs -text
|
444 |
.venv/lib/python3.11/site-packages/sympy/simplify/tests/__pycache__/test_simplify.cpython-311.pyc filter=lfs diff=lfs merge=lfs -text
|
445 |
+
.venv/lib/python3.11/site-packages/sympy/plotting/__pycache__/series.cpython-311.pyc filter=lfs diff=lfs merge=lfs -text
|
446 |
+
.venv/lib/python3.11/site-packages/sympy/plotting/tests/__pycache__/test_series.cpython-311.pyc filter=lfs diff=lfs merge=lfs -text
|
.venv/lib/python3.11/site-packages/sympy/matrices/__pycache__/__init__.cpython-311.pyc
ADDED
Binary file (3.39 kB). View file
|
|
.venv/lib/python3.11/site-packages/sympy/matrices/__pycache__/decompositions.cpython-311.pyc
ADDED
Binary file (56 kB). View file
|
|
.venv/lib/python3.11/site-packages/sympy/matrices/__pycache__/determinant.cpython-311.pyc
ADDED
Binary file (38.7 kB). View file
|
|
.venv/lib/python3.11/site-packages/sympy/matrices/__pycache__/eigen.cpython-311.pyc
ADDED
Binary file (55.7 kB). View file
|
|
.venv/lib/python3.11/site-packages/sympy/matrices/__pycache__/exceptions.cpython-311.pyc
ADDED
Binary file (1.51 kB). View file
|
|
.venv/lib/python3.11/site-packages/sympy/matrices/__pycache__/graph.cpython-311.pyc
ADDED
Binary file (10.5 kB). View file
|
|
.venv/lib/python3.11/site-packages/sympy/matrices/__pycache__/immutable.cpython-311.pyc
ADDED
Binary file (9.29 kB). View file
|
|
.venv/lib/python3.11/site-packages/sympy/matrices/__pycache__/inverse.cpython-311.pyc
ADDED
Binary file (18.7 kB). View file
|
|
.venv/lib/python3.11/site-packages/sympy/matrices/__pycache__/matrices.cpython-311.pyc
ADDED
Binary file (35.1 kB). View file
|
|
.venv/lib/python3.11/site-packages/sympy/matrices/__pycache__/reductions.cpython-311.pyc
ADDED
Binary file (15.8 kB). View file
|
|
.venv/lib/python3.11/site-packages/sympy/matrices/__pycache__/repmatrix.cpython-311.pyc
ADDED
Binary file (43.1 kB). View file
|
|
.venv/lib/python3.11/site-packages/sympy/matrices/__pycache__/solvers.cpython-311.pyc
ADDED
Binary file (34.9 kB). View file
|
|
.venv/lib/python3.11/site-packages/sympy/matrices/__pycache__/sparse.cpython-311.pyc
ADDED
Binary file (20.9 kB). View file
|
|
.venv/lib/python3.11/site-packages/sympy/matrices/__pycache__/sparsetools.cpython-311.pyc
ADDED
Binary file (12.2 kB). View file
|
|
.venv/lib/python3.11/site-packages/sympy/matrices/__pycache__/subspaces.cpython-311.pyc
ADDED
Binary file (6.01 kB). View file
|
|
.venv/lib/python3.11/site-packages/sympy/matrices/__pycache__/utilities.cpython-311.pyc
ADDED
Binary file (3.59 kB). View file
|
|
.venv/lib/python3.11/site-packages/sympy/matrices/common.py
ADDED
@@ -0,0 +1,3263 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
A module contining deprecated matrix mixin classes.
|
3 |
+
|
4 |
+
The classes in this module are deprecated and will be removed in a future
|
5 |
+
release. They are kept here for backwards compatibility in case downstream
|
6 |
+
code was subclassing them.
|
7 |
+
|
8 |
+
Importing anything else from this module is deprecated so anything here
|
9 |
+
should either not be used or should be imported from somewhere else.
|
10 |
+
"""
|
11 |
+
|
12 |
+
from collections import defaultdict
|
13 |
+
from collections.abc import Iterable
|
14 |
+
from inspect import isfunction
|
15 |
+
from functools import reduce
|
16 |
+
|
17 |
+
from sympy.assumptions.refine import refine
|
18 |
+
from sympy.core import SympifyError, Add
|
19 |
+
from sympy.core.basic import Atom
|
20 |
+
from sympy.core.decorators import call_highest_priority
|
21 |
+
from sympy.core.logic import fuzzy_and, FuzzyBool
|
22 |
+
from sympy.core.numbers import Integer
|
23 |
+
from sympy.core.mod import Mod
|
24 |
+
from sympy.core.singleton import S
|
25 |
+
from sympy.core.symbol import Symbol
|
26 |
+
from sympy.core.sympify import sympify
|
27 |
+
from sympy.functions.elementary.complexes import Abs, re, im
|
28 |
+
from sympy.utilities.exceptions import sympy_deprecation_warning
|
29 |
+
from .utilities import _dotprodsimp, _simplify
|
30 |
+
from sympy.polys.polytools import Poly
|
31 |
+
from sympy.utilities.iterables import flatten, is_sequence
|
32 |
+
from sympy.utilities.misc import as_int, filldedent
|
33 |
+
from sympy.tensor.array import NDimArray
|
34 |
+
|
35 |
+
from .utilities import _get_intermediate_simp_bool
|
36 |
+
|
37 |
+
|
38 |
+
# These exception types were previously defined in this module but were moved
|
39 |
+
# to exceptions.py. We reimport them here for backwards compatibility in case
|
40 |
+
# downstream code was importing them from here.
|
41 |
+
from .exceptions import ( # noqa: F401
|
42 |
+
MatrixError, ShapeError, NonSquareMatrixError, NonInvertibleMatrixError,
|
43 |
+
NonPositiveDefiniteMatrixError
|
44 |
+
)
|
45 |
+
|
46 |
+
|
47 |
+
_DEPRECATED_MIXINS = (
|
48 |
+
'MatrixShaping',
|
49 |
+
'MatrixSpecial',
|
50 |
+
'MatrixProperties',
|
51 |
+
'MatrixOperations',
|
52 |
+
'MatrixArithmetic',
|
53 |
+
'MatrixCommon',
|
54 |
+
'MatrixDeterminant',
|
55 |
+
'MatrixReductions',
|
56 |
+
'MatrixSubspaces',
|
57 |
+
'MatrixEigen',
|
58 |
+
'MatrixCalculus',
|
59 |
+
'MatrixDeprecated',
|
60 |
+
)
|
61 |
+
|
62 |
+
|
63 |
+
class _MatrixDeprecatedMeta(type):
|
64 |
+
|
65 |
+
#
|
66 |
+
# Override the default __instancecheck__ implementation to ensure that
|
67 |
+
# e.g. isinstance(M, MatrixCommon) still works when M is one of the
|
68 |
+
# matrix classes. Matrix no longer inherits from MatrixCommon so
|
69 |
+
# isinstance(M, MatrixCommon) would now return False by default.
|
70 |
+
#
|
71 |
+
# There were lots of places in the codebase where this was being done
|
72 |
+
# so it seems likely that downstream code may be doing it too. All use
|
73 |
+
# of these mixins is deprecated though so we give a deprecation warning
|
74 |
+
# unconditionally if they are being used with isinstance.
|
75 |
+
#
|
76 |
+
# Any code seeing this deprecation warning should be changed to use
|
77 |
+
# isinstance(M, MatrixBase) instead which also works in previous versions
|
78 |
+
# of SymPy.
|
79 |
+
#
|
80 |
+
|
81 |
+
def __instancecheck__(cls, instance):
|
82 |
+
|
83 |
+
sympy_deprecation_warning(
|
84 |
+
f"""
|
85 |
+
Checking whether an object is an instance of {cls.__name__} is
|
86 |
+
deprecated.
|
87 |
+
|
88 |
+
Use `isinstance(obj, Matrix)` instead of `isinstance(obj, {cls.__name__})`.
|
89 |
+
""",
|
90 |
+
deprecated_since_version="1.13",
|
91 |
+
active_deprecations_target="deprecated-matrix-mixins",
|
92 |
+
stacklevel=3,
|
93 |
+
)
|
94 |
+
|
95 |
+
from sympy.matrices.matrixbase import MatrixBase
|
96 |
+
from sympy.matrices.matrices import (
|
97 |
+
MatrixDeterminant,
|
98 |
+
MatrixReductions,
|
99 |
+
MatrixSubspaces,
|
100 |
+
MatrixEigen,
|
101 |
+
MatrixCalculus,
|
102 |
+
MatrixDeprecated
|
103 |
+
)
|
104 |
+
|
105 |
+
all_mixins = (
|
106 |
+
MatrixRequired,
|
107 |
+
MatrixShaping,
|
108 |
+
MatrixSpecial,
|
109 |
+
MatrixProperties,
|
110 |
+
MatrixOperations,
|
111 |
+
MatrixArithmetic,
|
112 |
+
MatrixCommon,
|
113 |
+
MatrixDeterminant,
|
114 |
+
MatrixReductions,
|
115 |
+
MatrixSubspaces,
|
116 |
+
MatrixEigen,
|
117 |
+
MatrixCalculus,
|
118 |
+
MatrixDeprecated
|
119 |
+
)
|
120 |
+
|
121 |
+
if cls in all_mixins and isinstance(instance, MatrixBase):
|
122 |
+
return True
|
123 |
+
else:
|
124 |
+
return super().__instancecheck__(instance)
|
125 |
+
|
126 |
+
|
127 |
+
class MatrixRequired(metaclass=_MatrixDeprecatedMeta):
|
128 |
+
"""Deprecated mixin class for making matrix classes."""
|
129 |
+
|
130 |
+
rows = None # type: int
|
131 |
+
cols = None # type: int
|
132 |
+
_simplify = None
|
133 |
+
|
134 |
+
def __init_subclass__(cls, **kwargs):
|
135 |
+
|
136 |
+
# Warn if any downstream code is subclassing this class or any of the
|
137 |
+
# deprecated mixin classes that are all ultimately subclasses of this
|
138 |
+
# class.
|
139 |
+
#
|
140 |
+
# We don't want to warn about the deprecated mixins themselves being
|
141 |
+
# created, but only about them being used as mixins by downstream code.
|
142 |
+
# Otherwise just importing this module would trigger a warning.
|
143 |
+
# Ultimately the whole module should be deprecated and removed but for
|
144 |
+
# SymPy 1.13 it is premature to do that given that this module was the
|
145 |
+
# main way to import matrix exception types in all previous versions.
|
146 |
+
|
147 |
+
if cls.__name__ not in _DEPRECATED_MIXINS:
|
148 |
+
sympy_deprecation_warning(
|
149 |
+
f"""
|
150 |
+
Inheriting from the Matrix mixin classes is deprecated.
|
151 |
+
|
152 |
+
The class {cls.__name__} is subclassing a deprecated mixin.
|
153 |
+
""",
|
154 |
+
deprecated_since_version="1.13",
|
155 |
+
active_deprecations_target="deprecated-matrix-mixins",
|
156 |
+
stacklevel=3,
|
157 |
+
)
|
158 |
+
|
159 |
+
super().__init_subclass__(**kwargs)
|
160 |
+
|
161 |
+
@classmethod
|
162 |
+
def _new(cls, *args, **kwargs):
|
163 |
+
"""`_new` must, at minimum, be callable as
|
164 |
+
`_new(rows, cols, mat) where mat is a flat list of the
|
165 |
+
elements of the matrix."""
|
166 |
+
raise NotImplementedError("Subclasses must implement this.")
|
167 |
+
|
168 |
+
def __eq__(self, other):
|
169 |
+
raise NotImplementedError("Subclasses must implement this.")
|
170 |
+
|
171 |
+
def __getitem__(self, key):
|
172 |
+
"""Implementations of __getitem__ should accept ints, in which
|
173 |
+
case the matrix is indexed as a flat list, tuples (i,j) in which
|
174 |
+
case the (i,j) entry is returned, slices, or mixed tuples (a,b)
|
175 |
+
where a and b are any combination of slices and integers."""
|
176 |
+
raise NotImplementedError("Subclasses must implement this.")
|
177 |
+
|
178 |
+
def __len__(self):
|
179 |
+
"""The total number of entries in the matrix."""
|
180 |
+
raise NotImplementedError("Subclasses must implement this.")
|
181 |
+
|
182 |
+
@property
|
183 |
+
def shape(self):
|
184 |
+
raise NotImplementedError("Subclasses must implement this.")
|
185 |
+
|
186 |
+
|
187 |
+
class MatrixShaping(MatrixRequired):
|
188 |
+
"""Provides basic matrix shaping and extracting of submatrices"""
|
189 |
+
|
190 |
+
def _eval_col_del(self, col):
|
191 |
+
def entry(i, j):
|
192 |
+
return self[i, j] if j < col else self[i, j + 1]
|
193 |
+
return self._new(self.rows, self.cols - 1, entry)
|
194 |
+
|
195 |
+
def _eval_col_insert(self, pos, other):
|
196 |
+
|
197 |
+
def entry(i, j):
|
198 |
+
if j < pos:
|
199 |
+
return self[i, j]
|
200 |
+
elif pos <= j < pos + other.cols:
|
201 |
+
return other[i, j - pos]
|
202 |
+
return self[i, j - other.cols]
|
203 |
+
|
204 |
+
return self._new(self.rows, self.cols + other.cols, entry)
|
205 |
+
|
206 |
+
def _eval_col_join(self, other):
|
207 |
+
rows = self.rows
|
208 |
+
|
209 |
+
def entry(i, j):
|
210 |
+
if i < rows:
|
211 |
+
return self[i, j]
|
212 |
+
return other[i - rows, j]
|
213 |
+
|
214 |
+
return classof(self, other)._new(self.rows + other.rows, self.cols,
|
215 |
+
entry)
|
216 |
+
|
217 |
+
def _eval_extract(self, rowsList, colsList):
|
218 |
+
mat = list(self)
|
219 |
+
cols = self.cols
|
220 |
+
indices = (i * cols + j for i in rowsList for j in colsList)
|
221 |
+
return self._new(len(rowsList), len(colsList),
|
222 |
+
[mat[i] for i in indices])
|
223 |
+
|
224 |
+
def _eval_get_diag_blocks(self):
|
225 |
+
sub_blocks = []
|
226 |
+
|
227 |
+
def recurse_sub_blocks(M):
|
228 |
+
i = 1
|
229 |
+
while i <= M.shape[0]:
|
230 |
+
if i == 1:
|
231 |
+
to_the_right = M[0, i:]
|
232 |
+
to_the_bottom = M[i:, 0]
|
233 |
+
else:
|
234 |
+
to_the_right = M[:i, i:]
|
235 |
+
to_the_bottom = M[i:, :i]
|
236 |
+
if any(to_the_right) or any(to_the_bottom):
|
237 |
+
i += 1
|
238 |
+
continue
|
239 |
+
else:
|
240 |
+
sub_blocks.append(M[:i, :i])
|
241 |
+
if M.shape == M[:i, :i].shape:
|
242 |
+
return
|
243 |
+
else:
|
244 |
+
recurse_sub_blocks(M[i:, i:])
|
245 |
+
return
|
246 |
+
|
247 |
+
recurse_sub_blocks(self)
|
248 |
+
return sub_blocks
|
249 |
+
|
250 |
+
def _eval_row_del(self, row):
|
251 |
+
def entry(i, j):
|
252 |
+
return self[i, j] if i < row else self[i + 1, j]
|
253 |
+
return self._new(self.rows - 1, self.cols, entry)
|
254 |
+
|
255 |
+
def _eval_row_insert(self, pos, other):
|
256 |
+
entries = list(self)
|
257 |
+
insert_pos = pos * self.cols
|
258 |
+
entries[insert_pos:insert_pos] = list(other)
|
259 |
+
return self._new(self.rows + other.rows, self.cols, entries)
|
260 |
+
|
261 |
+
def _eval_row_join(self, other):
|
262 |
+
cols = self.cols
|
263 |
+
|
264 |
+
def entry(i, j):
|
265 |
+
if j < cols:
|
266 |
+
return self[i, j]
|
267 |
+
return other[i, j - cols]
|
268 |
+
|
269 |
+
return classof(self, other)._new(self.rows, self.cols + other.cols,
|
270 |
+
entry)
|
271 |
+
|
272 |
+
def _eval_tolist(self):
|
273 |
+
return [list(self[i,:]) for i in range(self.rows)]
|
274 |
+
|
275 |
+
def _eval_todok(self):
|
276 |
+
dok = {}
|
277 |
+
rows, cols = self.shape
|
278 |
+
for i in range(rows):
|
279 |
+
for j in range(cols):
|
280 |
+
val = self[i, j]
|
281 |
+
if val != self.zero:
|
282 |
+
dok[i, j] = val
|
283 |
+
return dok
|
284 |
+
|
285 |
+
def _eval_vec(self):
|
286 |
+
rows = self.rows
|
287 |
+
|
288 |
+
def entry(n, _):
|
289 |
+
# we want to read off the columns first
|
290 |
+
j = n // rows
|
291 |
+
i = n - j * rows
|
292 |
+
return self[i, j]
|
293 |
+
|
294 |
+
return self._new(len(self), 1, entry)
|
295 |
+
|
296 |
+
def _eval_vech(self, diagonal):
|
297 |
+
c = self.cols
|
298 |
+
v = []
|
299 |
+
if diagonal:
|
300 |
+
for j in range(c):
|
301 |
+
for i in range(j, c):
|
302 |
+
v.append(self[i, j])
|
303 |
+
else:
|
304 |
+
for j in range(c):
|
305 |
+
for i in range(j + 1, c):
|
306 |
+
v.append(self[i, j])
|
307 |
+
return self._new(len(v), 1, v)
|
308 |
+
|
309 |
+
def col_del(self, col):
|
310 |
+
"""Delete the specified column."""
|
311 |
+
if col < 0:
|
312 |
+
col += self.cols
|
313 |
+
if not 0 <= col < self.cols:
|
314 |
+
raise IndexError("Column {} is out of range.".format(col))
|
315 |
+
return self._eval_col_del(col)
|
316 |
+
|
317 |
+
def col_insert(self, pos, other):
|
318 |
+
"""Insert one or more columns at the given column position.
|
319 |
+
|
320 |
+
Examples
|
321 |
+
========
|
322 |
+
|
323 |
+
>>> from sympy import zeros, ones
|
324 |
+
>>> M = zeros(3)
|
325 |
+
>>> V = ones(3, 1)
|
326 |
+
>>> M.col_insert(1, V)
|
327 |
+
Matrix([
|
328 |
+
[0, 1, 0, 0],
|
329 |
+
[0, 1, 0, 0],
|
330 |
+
[0, 1, 0, 0]])
|
331 |
+
|
332 |
+
See Also
|
333 |
+
========
|
334 |
+
|
335 |
+
col
|
336 |
+
row_insert
|
337 |
+
"""
|
338 |
+
# Allows you to build a matrix even if it is null matrix
|
339 |
+
if not self:
|
340 |
+
return type(self)(other)
|
341 |
+
|
342 |
+
pos = as_int(pos)
|
343 |
+
|
344 |
+
if pos < 0:
|
345 |
+
pos = self.cols + pos
|
346 |
+
if pos < 0:
|
347 |
+
pos = 0
|
348 |
+
elif pos > self.cols:
|
349 |
+
pos = self.cols
|
350 |
+
|
351 |
+
if self.rows != other.rows:
|
352 |
+
raise ShapeError(
|
353 |
+
"The matrices have incompatible number of rows ({} and {})"
|
354 |
+
.format(self.rows, other.rows))
|
355 |
+
|
356 |
+
return self._eval_col_insert(pos, other)
|
357 |
+
|
358 |
+
def col_join(self, other):
|
359 |
+
"""Concatenates two matrices along self's last and other's first row.
|
360 |
+
|
361 |
+
Examples
|
362 |
+
========
|
363 |
+
|
364 |
+
>>> from sympy import zeros, ones
|
365 |
+
>>> M = zeros(3)
|
366 |
+
>>> V = ones(1, 3)
|
367 |
+
>>> M.col_join(V)
|
368 |
+
Matrix([
|
369 |
+
[0, 0, 0],
|
370 |
+
[0, 0, 0],
|
371 |
+
[0, 0, 0],
|
372 |
+
[1, 1, 1]])
|
373 |
+
|
374 |
+
See Also
|
375 |
+
========
|
376 |
+
|
377 |
+
col
|
378 |
+
row_join
|
379 |
+
"""
|
380 |
+
# A null matrix can always be stacked (see #10770)
|
381 |
+
if self.rows == 0 and self.cols != other.cols:
|
382 |
+
return self._new(0, other.cols, []).col_join(other)
|
383 |
+
|
384 |
+
if self.cols != other.cols:
|
385 |
+
raise ShapeError(
|
386 |
+
"The matrices have incompatible number of columns ({} and {})"
|
387 |
+
.format(self.cols, other.cols))
|
388 |
+
return self._eval_col_join(other)
|
389 |
+
|
390 |
+
def col(self, j):
|
391 |
+
"""Elementary column selector.
|
392 |
+
|
393 |
+
Examples
|
394 |
+
========
|
395 |
+
|
396 |
+
>>> from sympy import eye
|
397 |
+
>>> eye(2).col(0)
|
398 |
+
Matrix([
|
399 |
+
[1],
|
400 |
+
[0]])
|
401 |
+
|
402 |
+
See Also
|
403 |
+
========
|
404 |
+
|
405 |
+
row
|
406 |
+
col_del
|
407 |
+
col_join
|
408 |
+
col_insert
|
409 |
+
"""
|
410 |
+
return self[:, j]
|
411 |
+
|
412 |
+
def extract(self, rowsList, colsList):
|
413 |
+
r"""Return a submatrix by specifying a list of rows and columns.
|
414 |
+
Negative indices can be given. All indices must be in the range
|
415 |
+
$-n \le i < n$ where $n$ is the number of rows or columns.
|
416 |
+
|
417 |
+
Examples
|
418 |
+
========
|
419 |
+
|
420 |
+
>>> from sympy import Matrix
|
421 |
+
>>> m = Matrix(4, 3, range(12))
|
422 |
+
>>> m
|
423 |
+
Matrix([
|
424 |
+
[0, 1, 2],
|
425 |
+
[3, 4, 5],
|
426 |
+
[6, 7, 8],
|
427 |
+
[9, 10, 11]])
|
428 |
+
>>> m.extract([0, 1, 3], [0, 1])
|
429 |
+
Matrix([
|
430 |
+
[0, 1],
|
431 |
+
[3, 4],
|
432 |
+
[9, 10]])
|
433 |
+
|
434 |
+
Rows or columns can be repeated:
|
435 |
+
|
436 |
+
>>> m.extract([0, 0, 1], [-1])
|
437 |
+
Matrix([
|
438 |
+
[2],
|
439 |
+
[2],
|
440 |
+
[5]])
|
441 |
+
|
442 |
+
Every other row can be taken by using range to provide the indices:
|
443 |
+
|
444 |
+
>>> m.extract(range(0, m.rows, 2), [-1])
|
445 |
+
Matrix([
|
446 |
+
[2],
|
447 |
+
[8]])
|
448 |
+
|
449 |
+
RowsList or colsList can also be a list of booleans, in which case
|
450 |
+
the rows or columns corresponding to the True values will be selected:
|
451 |
+
|
452 |
+
>>> m.extract([0, 1, 2, 3], [True, False, True])
|
453 |
+
Matrix([
|
454 |
+
[0, 2],
|
455 |
+
[3, 5],
|
456 |
+
[6, 8],
|
457 |
+
[9, 11]])
|
458 |
+
"""
|
459 |
+
|
460 |
+
if not is_sequence(rowsList) or not is_sequence(colsList):
|
461 |
+
raise TypeError("rowsList and colsList must be iterable")
|
462 |
+
# ensure rowsList and colsList are lists of integers
|
463 |
+
if rowsList and all(isinstance(i, bool) for i in rowsList):
|
464 |
+
rowsList = [index for index, item in enumerate(rowsList) if item]
|
465 |
+
if colsList and all(isinstance(i, bool) for i in colsList):
|
466 |
+
colsList = [index for index, item in enumerate(colsList) if item]
|
467 |
+
|
468 |
+
# ensure everything is in range
|
469 |
+
rowsList = [a2idx(k, self.rows) for k in rowsList]
|
470 |
+
colsList = [a2idx(k, self.cols) for k in colsList]
|
471 |
+
|
472 |
+
return self._eval_extract(rowsList, colsList)
|
473 |
+
|
474 |
+
def get_diag_blocks(self):
|
475 |
+
"""Obtains the square sub-matrices on the main diagonal of a square matrix.
|
476 |
+
|
477 |
+
Useful for inverting symbolic matrices or solving systems of
|
478 |
+
linear equations which may be decoupled by having a block diagonal
|
479 |
+
structure.
|
480 |
+
|
481 |
+
Examples
|
482 |
+
========
|
483 |
+
|
484 |
+
>>> from sympy import Matrix
|
485 |
+
>>> from sympy.abc import x, y, z
|
486 |
+
>>> A = Matrix([[1, 3, 0, 0], [y, z*z, 0, 0], [0, 0, x, 0], [0, 0, 0, 0]])
|
487 |
+
>>> a1, a2, a3 = A.get_diag_blocks()
|
488 |
+
>>> a1
|
489 |
+
Matrix([
|
490 |
+
[1, 3],
|
491 |
+
[y, z**2]])
|
492 |
+
>>> a2
|
493 |
+
Matrix([[x]])
|
494 |
+
>>> a3
|
495 |
+
Matrix([[0]])
|
496 |
+
|
497 |
+
"""
|
498 |
+
return self._eval_get_diag_blocks()
|
499 |
+
|
500 |
+
@classmethod
|
501 |
+
def hstack(cls, *args):
|
502 |
+
"""Return a matrix formed by joining args horizontally (i.e.
|
503 |
+
by repeated application of row_join).
|
504 |
+
|
505 |
+
Examples
|
506 |
+
========
|
507 |
+
|
508 |
+
>>> from sympy import Matrix, eye
|
509 |
+
>>> Matrix.hstack(eye(2), 2*eye(2))
|
510 |
+
Matrix([
|
511 |
+
[1, 0, 2, 0],
|
512 |
+
[0, 1, 0, 2]])
|
513 |
+
"""
|
514 |
+
if len(args) == 0:
|
515 |
+
return cls._new()
|
516 |
+
|
517 |
+
kls = type(args[0])
|
518 |
+
return reduce(kls.row_join, args)
|
519 |
+
|
520 |
+
def reshape(self, rows, cols):
|
521 |
+
"""Reshape the matrix. Total number of elements must remain the same.
|
522 |
+
|
523 |
+
Examples
|
524 |
+
========
|
525 |
+
|
526 |
+
>>> from sympy import Matrix
|
527 |
+
>>> m = Matrix(2, 3, lambda i, j: 1)
|
528 |
+
>>> m
|
529 |
+
Matrix([
|
530 |
+
[1, 1, 1],
|
531 |
+
[1, 1, 1]])
|
532 |
+
>>> m.reshape(1, 6)
|
533 |
+
Matrix([[1, 1, 1, 1, 1, 1]])
|
534 |
+
>>> m.reshape(3, 2)
|
535 |
+
Matrix([
|
536 |
+
[1, 1],
|
537 |
+
[1, 1],
|
538 |
+
[1, 1]])
|
539 |
+
|
540 |
+
"""
|
541 |
+
if self.rows * self.cols != rows * cols:
|
542 |
+
raise ValueError("Invalid reshape parameters %d %d" % (rows, cols))
|
543 |
+
return self._new(rows, cols, lambda i, j: self[i * cols + j])
|
544 |
+
|
545 |
+
def row_del(self, row):
|
546 |
+
"""Delete the specified row."""
|
547 |
+
if row < 0:
|
548 |
+
row += self.rows
|
549 |
+
if not 0 <= row < self.rows:
|
550 |
+
raise IndexError("Row {} is out of range.".format(row))
|
551 |
+
|
552 |
+
return self._eval_row_del(row)
|
553 |
+
|
554 |
+
def row_insert(self, pos, other):
|
555 |
+
"""Insert one or more rows at the given row position.
|
556 |
+
|
557 |
+
Examples
|
558 |
+
========
|
559 |
+
|
560 |
+
>>> from sympy import zeros, ones
|
561 |
+
>>> M = zeros(3)
|
562 |
+
>>> V = ones(1, 3)
|
563 |
+
>>> M.row_insert(1, V)
|
564 |
+
Matrix([
|
565 |
+
[0, 0, 0],
|
566 |
+
[1, 1, 1],
|
567 |
+
[0, 0, 0],
|
568 |
+
[0, 0, 0]])
|
569 |
+
|
570 |
+
See Also
|
571 |
+
========
|
572 |
+
|
573 |
+
row
|
574 |
+
col_insert
|
575 |
+
"""
|
576 |
+
# Allows you to build a matrix even if it is null matrix
|
577 |
+
if not self:
|
578 |
+
return self._new(other)
|
579 |
+
|
580 |
+
pos = as_int(pos)
|
581 |
+
|
582 |
+
if pos < 0:
|
583 |
+
pos = self.rows + pos
|
584 |
+
if pos < 0:
|
585 |
+
pos = 0
|
586 |
+
elif pos > self.rows:
|
587 |
+
pos = self.rows
|
588 |
+
|
589 |
+
if self.cols != other.cols:
|
590 |
+
raise ShapeError(
|
591 |
+
"The matrices have incompatible number of columns ({} and {})"
|
592 |
+
.format(self.cols, other.cols))
|
593 |
+
|
594 |
+
return self._eval_row_insert(pos, other)
|
595 |
+
|
596 |
+
def row_join(self, other):
|
597 |
+
"""Concatenates two matrices along self's last and rhs's first column
|
598 |
+
|
599 |
+
Examples
|
600 |
+
========
|
601 |
+
|
602 |
+
>>> from sympy import zeros, ones
|
603 |
+
>>> M = zeros(3)
|
604 |
+
>>> V = ones(3, 1)
|
605 |
+
>>> M.row_join(V)
|
606 |
+
Matrix([
|
607 |
+
[0, 0, 0, 1],
|
608 |
+
[0, 0, 0, 1],
|
609 |
+
[0, 0, 0, 1]])
|
610 |
+
|
611 |
+
See Also
|
612 |
+
========
|
613 |
+
|
614 |
+
row
|
615 |
+
col_join
|
616 |
+
"""
|
617 |
+
# A null matrix can always be stacked (see #10770)
|
618 |
+
if self.cols == 0 and self.rows != other.rows:
|
619 |
+
return self._new(other.rows, 0, []).row_join(other)
|
620 |
+
|
621 |
+
if self.rows != other.rows:
|
622 |
+
raise ShapeError(
|
623 |
+
"The matrices have incompatible number of rows ({} and {})"
|
624 |
+
.format(self.rows, other.rows))
|
625 |
+
return self._eval_row_join(other)
|
626 |
+
|
627 |
+
def diagonal(self, k=0):
|
628 |
+
"""Returns the kth diagonal of self. The main diagonal
|
629 |
+
corresponds to `k=0`; diagonals above and below correspond to
|
630 |
+
`k > 0` and `k < 0`, respectively. The values of `self[i, j]`
|
631 |
+
for which `j - i = k`, are returned in order of increasing
|
632 |
+
`i + j`, starting with `i + j = |k|`.
|
633 |
+
|
634 |
+
Examples
|
635 |
+
========
|
636 |
+
|
637 |
+
>>> from sympy import Matrix
|
638 |
+
>>> m = Matrix(3, 3, lambda i, j: j - i); m
|
639 |
+
Matrix([
|
640 |
+
[ 0, 1, 2],
|
641 |
+
[-1, 0, 1],
|
642 |
+
[-2, -1, 0]])
|
643 |
+
>>> _.diagonal()
|
644 |
+
Matrix([[0, 0, 0]])
|
645 |
+
>>> m.diagonal(1)
|
646 |
+
Matrix([[1, 1]])
|
647 |
+
>>> m.diagonal(-2)
|
648 |
+
Matrix([[-2]])
|
649 |
+
|
650 |
+
Even though the diagonal is returned as a Matrix, the element
|
651 |
+
retrieval can be done with a single index:
|
652 |
+
|
653 |
+
>>> Matrix.diag(1, 2, 3).diagonal()[1] # instead of [0, 1]
|
654 |
+
2
|
655 |
+
|
656 |
+
See Also
|
657 |
+
========
|
658 |
+
|
659 |
+
diag
|
660 |
+
"""
|
661 |
+
rv = []
|
662 |
+
k = as_int(k)
|
663 |
+
r = 0 if k > 0 else -k
|
664 |
+
c = 0 if r else k
|
665 |
+
while True:
|
666 |
+
if r == self.rows or c == self.cols:
|
667 |
+
break
|
668 |
+
rv.append(self[r, c])
|
669 |
+
r += 1
|
670 |
+
c += 1
|
671 |
+
if not rv:
|
672 |
+
raise ValueError(filldedent('''
|
673 |
+
The %s diagonal is out of range [%s, %s]''' % (
|
674 |
+
k, 1 - self.rows, self.cols - 1)))
|
675 |
+
return self._new(1, len(rv), rv)
|
676 |
+
|
677 |
+
def row(self, i):
|
678 |
+
"""Elementary row selector.
|
679 |
+
|
680 |
+
Examples
|
681 |
+
========
|
682 |
+
|
683 |
+
>>> from sympy import eye
|
684 |
+
>>> eye(2).row(0)
|
685 |
+
Matrix([[1, 0]])
|
686 |
+
|
687 |
+
See Also
|
688 |
+
========
|
689 |
+
|
690 |
+
col
|
691 |
+
row_del
|
692 |
+
row_join
|
693 |
+
row_insert
|
694 |
+
"""
|
695 |
+
return self[i, :]
|
696 |
+
|
697 |
+
@property
|
698 |
+
def shape(self):
|
699 |
+
"""The shape (dimensions) of the matrix as the 2-tuple (rows, cols).
|
700 |
+
|
701 |
+
Examples
|
702 |
+
========
|
703 |
+
|
704 |
+
>>> from sympy import zeros
|
705 |
+
>>> M = zeros(2, 3)
|
706 |
+
>>> M.shape
|
707 |
+
(2, 3)
|
708 |
+
>>> M.rows
|
709 |
+
2
|
710 |
+
>>> M.cols
|
711 |
+
3
|
712 |
+
"""
|
713 |
+
return (self.rows, self.cols)
|
714 |
+
|
715 |
+
def todok(self):
|
716 |
+
"""Return the matrix as dictionary of keys.
|
717 |
+
|
718 |
+
Examples
|
719 |
+
========
|
720 |
+
|
721 |
+
>>> from sympy import Matrix
|
722 |
+
>>> M = Matrix.eye(3)
|
723 |
+
>>> M.todok()
|
724 |
+
{(0, 0): 1, (1, 1): 1, (2, 2): 1}
|
725 |
+
"""
|
726 |
+
return self._eval_todok()
|
727 |
+
|
728 |
+
def tolist(self):
|
729 |
+
"""Return the Matrix as a nested Python list.
|
730 |
+
|
731 |
+
Examples
|
732 |
+
========
|
733 |
+
|
734 |
+
>>> from sympy import Matrix, ones
|
735 |
+
>>> m = Matrix(3, 3, range(9))
|
736 |
+
>>> m
|
737 |
+
Matrix([
|
738 |
+
[0, 1, 2],
|
739 |
+
[3, 4, 5],
|
740 |
+
[6, 7, 8]])
|
741 |
+
>>> m.tolist()
|
742 |
+
[[0, 1, 2], [3, 4, 5], [6, 7, 8]]
|
743 |
+
>>> ones(3, 0).tolist()
|
744 |
+
[[], [], []]
|
745 |
+
|
746 |
+
When there are no rows then it will not be possible to tell how
|
747 |
+
many columns were in the original matrix:
|
748 |
+
|
749 |
+
>>> ones(0, 3).tolist()
|
750 |
+
[]
|
751 |
+
|
752 |
+
"""
|
753 |
+
if not self.rows:
|
754 |
+
return []
|
755 |
+
if not self.cols:
|
756 |
+
return [[] for i in range(self.rows)]
|
757 |
+
return self._eval_tolist()
|
758 |
+
|
759 |
+
def todod(M):
|
760 |
+
"""Returns matrix as dict of dicts containing non-zero elements of the Matrix
|
761 |
+
|
762 |
+
Examples
|
763 |
+
========
|
764 |
+
|
765 |
+
>>> from sympy import Matrix
|
766 |
+
>>> A = Matrix([[0, 1],[0, 3]])
|
767 |
+
>>> A
|
768 |
+
Matrix([
|
769 |
+
[0, 1],
|
770 |
+
[0, 3]])
|
771 |
+
>>> A.todod()
|
772 |
+
{0: {1: 1}, 1: {1: 3}}
|
773 |
+
|
774 |
+
|
775 |
+
"""
|
776 |
+
rowsdict = {}
|
777 |
+
Mlol = M.tolist()
|
778 |
+
for i, Mi in enumerate(Mlol):
|
779 |
+
row = {j: Mij for j, Mij in enumerate(Mi) if Mij}
|
780 |
+
if row:
|
781 |
+
rowsdict[i] = row
|
782 |
+
return rowsdict
|
783 |
+
|
784 |
+
def vec(self):
|
785 |
+
"""Return the Matrix converted into a one column matrix by stacking columns
|
786 |
+
|
787 |
+
Examples
|
788 |
+
========
|
789 |
+
|
790 |
+
>>> from sympy import Matrix
|
791 |
+
>>> m=Matrix([[1, 3], [2, 4]])
|
792 |
+
>>> m
|
793 |
+
Matrix([
|
794 |
+
[1, 3],
|
795 |
+
[2, 4]])
|
796 |
+
>>> m.vec()
|
797 |
+
Matrix([
|
798 |
+
[1],
|
799 |
+
[2],
|
800 |
+
[3],
|
801 |
+
[4]])
|
802 |
+
|
803 |
+
See Also
|
804 |
+
========
|
805 |
+
|
806 |
+
vech
|
807 |
+
"""
|
808 |
+
return self._eval_vec()
|
809 |
+
|
810 |
+
def vech(self, diagonal=True, check_symmetry=True):
|
811 |
+
"""Reshapes the matrix into a column vector by stacking the
|
812 |
+
elements in the lower triangle.
|
813 |
+
|
814 |
+
Parameters
|
815 |
+
==========
|
816 |
+
|
817 |
+
diagonal : bool, optional
|
818 |
+
If ``True``, it includes the diagonal elements.
|
819 |
+
|
820 |
+
check_symmetry : bool, optional
|
821 |
+
If ``True``, it checks whether the matrix is symmetric.
|
822 |
+
|
823 |
+
Examples
|
824 |
+
========
|
825 |
+
|
826 |
+
>>> from sympy import Matrix
|
827 |
+
>>> m=Matrix([[1, 2], [2, 3]])
|
828 |
+
>>> m
|
829 |
+
Matrix([
|
830 |
+
[1, 2],
|
831 |
+
[2, 3]])
|
832 |
+
>>> m.vech()
|
833 |
+
Matrix([
|
834 |
+
[1],
|
835 |
+
[2],
|
836 |
+
[3]])
|
837 |
+
>>> m.vech(diagonal=False)
|
838 |
+
Matrix([[2]])
|
839 |
+
|
840 |
+
Notes
|
841 |
+
=====
|
842 |
+
|
843 |
+
This should work for symmetric matrices and ``vech`` can
|
844 |
+
represent symmetric matrices in vector form with less size than
|
845 |
+
``vec``.
|
846 |
+
|
847 |
+
See Also
|
848 |
+
========
|
849 |
+
|
850 |
+
vec
|
851 |
+
"""
|
852 |
+
if not self.is_square:
|
853 |
+
raise NonSquareMatrixError
|
854 |
+
|
855 |
+
if check_symmetry and not self.is_symmetric():
|
856 |
+
raise ValueError("The matrix is not symmetric.")
|
857 |
+
|
858 |
+
return self._eval_vech(diagonal)
|
859 |
+
|
860 |
+
@classmethod
|
861 |
+
def vstack(cls, *args):
|
862 |
+
"""Return a matrix formed by joining args vertically (i.e.
|
863 |
+
by repeated application of col_join).
|
864 |
+
|
865 |
+
Examples
|
866 |
+
========
|
867 |
+
|
868 |
+
>>> from sympy import Matrix, eye
|
869 |
+
>>> Matrix.vstack(eye(2), 2*eye(2))
|
870 |
+
Matrix([
|
871 |
+
[1, 0],
|
872 |
+
[0, 1],
|
873 |
+
[2, 0],
|
874 |
+
[0, 2]])
|
875 |
+
"""
|
876 |
+
if len(args) == 0:
|
877 |
+
return cls._new()
|
878 |
+
|
879 |
+
kls = type(args[0])
|
880 |
+
return reduce(kls.col_join, args)
|
881 |
+
|
882 |
+
|
883 |
+
class MatrixSpecial(MatrixRequired):
|
884 |
+
"""Construction of special matrices"""
|
885 |
+
|
886 |
+
@classmethod
|
887 |
+
def _eval_diag(cls, rows, cols, diag_dict):
|
888 |
+
"""diag_dict is a defaultdict containing
|
889 |
+
all the entries of the diagonal matrix."""
|
890 |
+
def entry(i, j):
|
891 |
+
return diag_dict[(i, j)]
|
892 |
+
return cls._new(rows, cols, entry)
|
893 |
+
|
894 |
+
@classmethod
|
895 |
+
def _eval_eye(cls, rows, cols):
|
896 |
+
vals = [cls.zero]*(rows*cols)
|
897 |
+
vals[::cols+1] = [cls.one]*min(rows, cols)
|
898 |
+
return cls._new(rows, cols, vals, copy=False)
|
899 |
+
|
900 |
+
@classmethod
|
901 |
+
def _eval_jordan_block(cls, size: int, eigenvalue, band='upper'):
|
902 |
+
if band == 'lower':
|
903 |
+
def entry(i, j):
|
904 |
+
if i == j:
|
905 |
+
return eigenvalue
|
906 |
+
elif j + 1 == i:
|
907 |
+
return cls.one
|
908 |
+
return cls.zero
|
909 |
+
else:
|
910 |
+
def entry(i, j):
|
911 |
+
if i == j:
|
912 |
+
return eigenvalue
|
913 |
+
elif i + 1 == j:
|
914 |
+
return cls.one
|
915 |
+
return cls.zero
|
916 |
+
return cls._new(size, size, entry)
|
917 |
+
|
918 |
+
@classmethod
|
919 |
+
def _eval_ones(cls, rows, cols):
|
920 |
+
def entry(i, j):
|
921 |
+
return cls.one
|
922 |
+
return cls._new(rows, cols, entry)
|
923 |
+
|
924 |
+
@classmethod
|
925 |
+
def _eval_zeros(cls, rows, cols):
|
926 |
+
return cls._new(rows, cols, [cls.zero]*(rows*cols), copy=False)
|
927 |
+
|
928 |
+
@classmethod
|
929 |
+
def _eval_wilkinson(cls, n):
|
930 |
+
def entry(i, j):
|
931 |
+
return cls.one if i + 1 == j else cls.zero
|
932 |
+
|
933 |
+
D = cls._new(2*n + 1, 2*n + 1, entry)
|
934 |
+
|
935 |
+
wminus = cls.diag(list(range(-n, n + 1)), unpack=True) + D + D.T
|
936 |
+
wplus = abs(cls.diag(list(range(-n, n + 1)), unpack=True)) + D + D.T
|
937 |
+
|
938 |
+
return wminus, wplus
|
939 |
+
|
940 |
+
@classmethod
|
941 |
+
def diag(kls, *args, strict=False, unpack=True, rows=None, cols=None, **kwargs):
|
942 |
+
"""Returns a matrix with the specified diagonal.
|
943 |
+
If matrices are passed, a block-diagonal matrix
|
944 |
+
is created (i.e. the "direct sum" of the matrices).
|
945 |
+
|
946 |
+
kwargs
|
947 |
+
======
|
948 |
+
|
949 |
+
rows : rows of the resulting matrix; computed if
|
950 |
+
not given.
|
951 |
+
|
952 |
+
cols : columns of the resulting matrix; computed if
|
953 |
+
not given.
|
954 |
+
|
955 |
+
cls : class for the resulting matrix
|
956 |
+
|
957 |
+
unpack : bool which, when True (default), unpacks a single
|
958 |
+
sequence rather than interpreting it as a Matrix.
|
959 |
+
|
960 |
+
strict : bool which, when False (default), allows Matrices to
|
961 |
+
have variable-length rows.
|
962 |
+
|
963 |
+
Examples
|
964 |
+
========
|
965 |
+
|
966 |
+
>>> from sympy import Matrix
|
967 |
+
>>> Matrix.diag(1, 2, 3)
|
968 |
+
Matrix([
|
969 |
+
[1, 0, 0],
|
970 |
+
[0, 2, 0],
|
971 |
+
[0, 0, 3]])
|
972 |
+
|
973 |
+
The current default is to unpack a single sequence. If this is
|
974 |
+
not desired, set `unpack=False` and it will be interpreted as
|
975 |
+
a matrix.
|
976 |
+
|
977 |
+
>>> Matrix.diag([1, 2, 3]) == Matrix.diag(1, 2, 3)
|
978 |
+
True
|
979 |
+
|
980 |
+
When more than one element is passed, each is interpreted as
|
981 |
+
something to put on the diagonal. Lists are converted to
|
982 |
+
matrices. Filling of the diagonal always continues from
|
983 |
+
the bottom right hand corner of the previous item: this
|
984 |
+
will create a block-diagonal matrix whether the matrices
|
985 |
+
are square or not.
|
986 |
+
|
987 |
+
>>> col = [1, 2, 3]
|
988 |
+
>>> row = [[4, 5]]
|
989 |
+
>>> Matrix.diag(col, row)
|
990 |
+
Matrix([
|
991 |
+
[1, 0, 0],
|
992 |
+
[2, 0, 0],
|
993 |
+
[3, 0, 0],
|
994 |
+
[0, 4, 5]])
|
995 |
+
|
996 |
+
When `unpack` is False, elements within a list need not all be
|
997 |
+
of the same length. Setting `strict` to True would raise a
|
998 |
+
ValueError for the following:
|
999 |
+
|
1000 |
+
>>> Matrix.diag([[1, 2, 3], [4, 5], [6]], unpack=False)
|
1001 |
+
Matrix([
|
1002 |
+
[1, 2, 3],
|
1003 |
+
[4, 5, 0],
|
1004 |
+
[6, 0, 0]])
|
1005 |
+
|
1006 |
+
The type of the returned matrix can be set with the ``cls``
|
1007 |
+
keyword.
|
1008 |
+
|
1009 |
+
>>> from sympy import ImmutableMatrix
|
1010 |
+
>>> from sympy.utilities.misc import func_name
|
1011 |
+
>>> func_name(Matrix.diag(1, cls=ImmutableMatrix))
|
1012 |
+
'ImmutableDenseMatrix'
|
1013 |
+
|
1014 |
+
A zero dimension matrix can be used to position the start of
|
1015 |
+
the filling at the start of an arbitrary row or column:
|
1016 |
+
|
1017 |
+
>>> from sympy import ones
|
1018 |
+
>>> r2 = ones(0, 2)
|
1019 |
+
>>> Matrix.diag(r2, 1, 2)
|
1020 |
+
Matrix([
|
1021 |
+
[0, 0, 1, 0],
|
1022 |
+
[0, 0, 0, 2]])
|
1023 |
+
|
1024 |
+
See Also
|
1025 |
+
========
|
1026 |
+
eye
|
1027 |
+
diagonal
|
1028 |
+
.dense.diag
|
1029 |
+
.expressions.blockmatrix.BlockMatrix
|
1030 |
+
.sparsetools.banded
|
1031 |
+
"""
|
1032 |
+
from sympy.matrices.matrixbase import MatrixBase
|
1033 |
+
from sympy.matrices.dense import Matrix
|
1034 |
+
from sympy.matrices import SparseMatrix
|
1035 |
+
klass = kwargs.get('cls', kls)
|
1036 |
+
if unpack and len(args) == 1 and is_sequence(args[0]) and \
|
1037 |
+
not isinstance(args[0], MatrixBase):
|
1038 |
+
args = args[0]
|
1039 |
+
|
1040 |
+
# fill a default dict with the diagonal entries
|
1041 |
+
diag_entries = defaultdict(int)
|
1042 |
+
rmax = cmax = 0 # keep track of the biggest index seen
|
1043 |
+
for m in args:
|
1044 |
+
if isinstance(m, list):
|
1045 |
+
if strict:
|
1046 |
+
# if malformed, Matrix will raise an error
|
1047 |
+
_ = Matrix(m)
|
1048 |
+
r, c = _.shape
|
1049 |
+
m = _.tolist()
|
1050 |
+
else:
|
1051 |
+
r, c, smat = SparseMatrix._handle_creation_inputs(m)
|
1052 |
+
for (i, j), _ in smat.items():
|
1053 |
+
diag_entries[(i + rmax, j + cmax)] = _
|
1054 |
+
m = [] # to skip process below
|
1055 |
+
elif hasattr(m, 'shape'): # a Matrix
|
1056 |
+
# convert to list of lists
|
1057 |
+
r, c = m.shape
|
1058 |
+
m = m.tolist()
|
1059 |
+
else: # in this case, we're a single value
|
1060 |
+
diag_entries[(rmax, cmax)] = m
|
1061 |
+
rmax += 1
|
1062 |
+
cmax += 1
|
1063 |
+
continue
|
1064 |
+
# process list of lists
|
1065 |
+
for i, mi in enumerate(m):
|
1066 |
+
for j, _ in enumerate(mi):
|
1067 |
+
diag_entries[(i + rmax, j + cmax)] = _
|
1068 |
+
rmax += r
|
1069 |
+
cmax += c
|
1070 |
+
if rows is None:
|
1071 |
+
rows, cols = cols, rows
|
1072 |
+
if rows is None:
|
1073 |
+
rows, cols = rmax, cmax
|
1074 |
+
else:
|
1075 |
+
cols = rows if cols is None else cols
|
1076 |
+
if rows < rmax or cols < cmax:
|
1077 |
+
raise ValueError(filldedent('''
|
1078 |
+
The constructed matrix is {} x {} but a size of {} x {}
|
1079 |
+
was specified.'''.format(rmax, cmax, rows, cols)))
|
1080 |
+
return klass._eval_diag(rows, cols, diag_entries)
|
1081 |
+
|
1082 |
+
@classmethod
|
1083 |
+
def eye(kls, rows, cols=None, **kwargs):
|
1084 |
+
"""Returns an identity matrix.
|
1085 |
+
|
1086 |
+
Parameters
|
1087 |
+
==========
|
1088 |
+
|
1089 |
+
rows : rows of the matrix
|
1090 |
+
cols : cols of the matrix (if None, cols=rows)
|
1091 |
+
|
1092 |
+
kwargs
|
1093 |
+
======
|
1094 |
+
cls : class of the returned matrix
|
1095 |
+
"""
|
1096 |
+
if cols is None:
|
1097 |
+
cols = rows
|
1098 |
+
if rows < 0 or cols < 0:
|
1099 |
+
raise ValueError("Cannot create a {} x {} matrix. "
|
1100 |
+
"Both dimensions must be positive".format(rows, cols))
|
1101 |
+
klass = kwargs.get('cls', kls)
|
1102 |
+
rows, cols = as_int(rows), as_int(cols)
|
1103 |
+
|
1104 |
+
return klass._eval_eye(rows, cols)
|
1105 |
+
|
1106 |
+
@classmethod
|
1107 |
+
def jordan_block(kls, size=None, eigenvalue=None, *, band='upper', **kwargs):
|
1108 |
+
"""Returns a Jordan block
|
1109 |
+
|
1110 |
+
Parameters
|
1111 |
+
==========
|
1112 |
+
|
1113 |
+
size : Integer, optional
|
1114 |
+
Specifies the shape of the Jordan block matrix.
|
1115 |
+
|
1116 |
+
eigenvalue : Number or Symbol
|
1117 |
+
Specifies the value for the main diagonal of the matrix.
|
1118 |
+
|
1119 |
+
.. note::
|
1120 |
+
The keyword ``eigenval`` is also specified as an alias
|
1121 |
+
of this keyword, but it is not recommended to use.
|
1122 |
+
|
1123 |
+
We may deprecate the alias in later release.
|
1124 |
+
|
1125 |
+
band : 'upper' or 'lower', optional
|
1126 |
+
Specifies the position of the off-diagonal to put `1` s on.
|
1127 |
+
|
1128 |
+
cls : Matrix, optional
|
1129 |
+
Specifies the matrix class of the output form.
|
1130 |
+
|
1131 |
+
If it is not specified, the class type where the method is
|
1132 |
+
being executed on will be returned.
|
1133 |
+
|
1134 |
+
Returns
|
1135 |
+
=======
|
1136 |
+
|
1137 |
+
Matrix
|
1138 |
+
A Jordan block matrix.
|
1139 |
+
|
1140 |
+
Raises
|
1141 |
+
======
|
1142 |
+
|
1143 |
+
ValueError
|
1144 |
+
If insufficient arguments are given for matrix size
|
1145 |
+
specification, or no eigenvalue is given.
|
1146 |
+
|
1147 |
+
Examples
|
1148 |
+
========
|
1149 |
+
|
1150 |
+
Creating a default Jordan block:
|
1151 |
+
|
1152 |
+
>>> from sympy import Matrix
|
1153 |
+
>>> from sympy.abc import x
|
1154 |
+
>>> Matrix.jordan_block(4, x)
|
1155 |
+
Matrix([
|
1156 |
+
[x, 1, 0, 0],
|
1157 |
+
[0, x, 1, 0],
|
1158 |
+
[0, 0, x, 1],
|
1159 |
+
[0, 0, 0, x]])
|
1160 |
+
|
1161 |
+
Creating an alternative Jordan block matrix where `1` is on
|
1162 |
+
lower off-diagonal:
|
1163 |
+
|
1164 |
+
>>> Matrix.jordan_block(4, x, band='lower')
|
1165 |
+
Matrix([
|
1166 |
+
[x, 0, 0, 0],
|
1167 |
+
[1, x, 0, 0],
|
1168 |
+
[0, 1, x, 0],
|
1169 |
+
[0, 0, 1, x]])
|
1170 |
+
|
1171 |
+
Creating a Jordan block with keyword arguments
|
1172 |
+
|
1173 |
+
>>> Matrix.jordan_block(size=4, eigenvalue=x)
|
1174 |
+
Matrix([
|
1175 |
+
[x, 1, 0, 0],
|
1176 |
+
[0, x, 1, 0],
|
1177 |
+
[0, 0, x, 1],
|
1178 |
+
[0, 0, 0, x]])
|
1179 |
+
|
1180 |
+
References
|
1181 |
+
==========
|
1182 |
+
|
1183 |
+
.. [1] https://en.wikipedia.org/wiki/Jordan_matrix
|
1184 |
+
"""
|
1185 |
+
klass = kwargs.pop('cls', kls)
|
1186 |
+
|
1187 |
+
eigenval = kwargs.get('eigenval', None)
|
1188 |
+
if eigenvalue is None and eigenval is None:
|
1189 |
+
raise ValueError("Must supply an eigenvalue")
|
1190 |
+
elif eigenvalue != eigenval and None not in (eigenval, eigenvalue):
|
1191 |
+
raise ValueError(
|
1192 |
+
"Inconsistent values are given: 'eigenval'={}, "
|
1193 |
+
"'eigenvalue'={}".format(eigenval, eigenvalue))
|
1194 |
+
else:
|
1195 |
+
if eigenval is not None:
|
1196 |
+
eigenvalue = eigenval
|
1197 |
+
|
1198 |
+
if size is None:
|
1199 |
+
raise ValueError("Must supply a matrix size")
|
1200 |
+
|
1201 |
+
size = as_int(size)
|
1202 |
+
return klass._eval_jordan_block(size, eigenvalue, band)
|
1203 |
+
|
1204 |
+
@classmethod
|
1205 |
+
def ones(kls, rows, cols=None, **kwargs):
|
1206 |
+
"""Returns a matrix of ones.
|
1207 |
+
|
1208 |
+
Parameters
|
1209 |
+
==========
|
1210 |
+
|
1211 |
+
rows : rows of the matrix
|
1212 |
+
cols : cols of the matrix (if None, cols=rows)
|
1213 |
+
|
1214 |
+
kwargs
|
1215 |
+
======
|
1216 |
+
cls : class of the returned matrix
|
1217 |
+
"""
|
1218 |
+
if cols is None:
|
1219 |
+
cols = rows
|
1220 |
+
klass = kwargs.get('cls', kls)
|
1221 |
+
rows, cols = as_int(rows), as_int(cols)
|
1222 |
+
|
1223 |
+
return klass._eval_ones(rows, cols)
|
1224 |
+
|
1225 |
+
@classmethod
|
1226 |
+
def zeros(kls, rows, cols=None, **kwargs):
|
1227 |
+
"""Returns a matrix of zeros.
|
1228 |
+
|
1229 |
+
Parameters
|
1230 |
+
==========
|
1231 |
+
|
1232 |
+
rows : rows of the matrix
|
1233 |
+
cols : cols of the matrix (if None, cols=rows)
|
1234 |
+
|
1235 |
+
kwargs
|
1236 |
+
======
|
1237 |
+
cls : class of the returned matrix
|
1238 |
+
"""
|
1239 |
+
if cols is None:
|
1240 |
+
cols = rows
|
1241 |
+
if rows < 0 or cols < 0:
|
1242 |
+
raise ValueError("Cannot create a {} x {} matrix. "
|
1243 |
+
"Both dimensions must be positive".format(rows, cols))
|
1244 |
+
klass = kwargs.get('cls', kls)
|
1245 |
+
rows, cols = as_int(rows), as_int(cols)
|
1246 |
+
|
1247 |
+
return klass._eval_zeros(rows, cols)
|
1248 |
+
|
1249 |
+
@classmethod
|
1250 |
+
def companion(kls, poly):
|
1251 |
+
"""Returns a companion matrix of a polynomial.
|
1252 |
+
|
1253 |
+
Examples
|
1254 |
+
========
|
1255 |
+
|
1256 |
+
>>> from sympy import Matrix, Poly, Symbol, symbols
|
1257 |
+
>>> x = Symbol('x')
|
1258 |
+
>>> c0, c1, c2, c3, c4 = symbols('c0:5')
|
1259 |
+
>>> p = Poly(c0 + c1*x + c2*x**2 + c3*x**3 + c4*x**4 + x**5, x)
|
1260 |
+
>>> Matrix.companion(p)
|
1261 |
+
Matrix([
|
1262 |
+
[0, 0, 0, 0, -c0],
|
1263 |
+
[1, 0, 0, 0, -c1],
|
1264 |
+
[0, 1, 0, 0, -c2],
|
1265 |
+
[0, 0, 1, 0, -c3],
|
1266 |
+
[0, 0, 0, 1, -c4]])
|
1267 |
+
"""
|
1268 |
+
poly = kls._sympify(poly)
|
1269 |
+
if not isinstance(poly, Poly):
|
1270 |
+
raise ValueError("{} must be a Poly instance.".format(poly))
|
1271 |
+
if not poly.is_monic:
|
1272 |
+
raise ValueError("{} must be a monic polynomial.".format(poly))
|
1273 |
+
if not poly.is_univariate:
|
1274 |
+
raise ValueError(
|
1275 |
+
"{} must be a univariate polynomial.".format(poly))
|
1276 |
+
|
1277 |
+
size = poly.degree()
|
1278 |
+
if not size >= 1:
|
1279 |
+
raise ValueError(
|
1280 |
+
"{} must have degree not less than 1.".format(poly))
|
1281 |
+
|
1282 |
+
coeffs = poly.all_coeffs()
|
1283 |
+
def entry(i, j):
|
1284 |
+
if j == size - 1:
|
1285 |
+
return -coeffs[-1 - i]
|
1286 |
+
elif i == j + 1:
|
1287 |
+
return kls.one
|
1288 |
+
return kls.zero
|
1289 |
+
return kls._new(size, size, entry)
|
1290 |
+
|
1291 |
+
|
1292 |
+
@classmethod
|
1293 |
+
def wilkinson(kls, n, **kwargs):
|
1294 |
+
"""Returns two square Wilkinson Matrix of size 2*n + 1
|
1295 |
+
$W_{2n + 1}^-, W_{2n + 1}^+ =$ Wilkinson(n)
|
1296 |
+
|
1297 |
+
Examples
|
1298 |
+
========
|
1299 |
+
|
1300 |
+
>>> from sympy import Matrix
|
1301 |
+
>>> wminus, wplus = Matrix.wilkinson(3)
|
1302 |
+
>>> wminus
|
1303 |
+
Matrix([
|
1304 |
+
[-3, 1, 0, 0, 0, 0, 0],
|
1305 |
+
[ 1, -2, 1, 0, 0, 0, 0],
|
1306 |
+
[ 0, 1, -1, 1, 0, 0, 0],
|
1307 |
+
[ 0, 0, 1, 0, 1, 0, 0],
|
1308 |
+
[ 0, 0, 0, 1, 1, 1, 0],
|
1309 |
+
[ 0, 0, 0, 0, 1, 2, 1],
|
1310 |
+
[ 0, 0, 0, 0, 0, 1, 3]])
|
1311 |
+
>>> wplus
|
1312 |
+
Matrix([
|
1313 |
+
[3, 1, 0, 0, 0, 0, 0],
|
1314 |
+
[1, 2, 1, 0, 0, 0, 0],
|
1315 |
+
[0, 1, 1, 1, 0, 0, 0],
|
1316 |
+
[0, 0, 1, 0, 1, 0, 0],
|
1317 |
+
[0, 0, 0, 1, 1, 1, 0],
|
1318 |
+
[0, 0, 0, 0, 1, 2, 1],
|
1319 |
+
[0, 0, 0, 0, 0, 1, 3]])
|
1320 |
+
|
1321 |
+
References
|
1322 |
+
==========
|
1323 |
+
|
1324 |
+
.. [1] https://blogs.mathworks.com/cleve/2013/04/15/wilkinsons-matrices-2/
|
1325 |
+
.. [2] J. H. Wilkinson, The Algebraic Eigenvalue Problem, Claredon Press, Oxford, 1965, 662 pp.
|
1326 |
+
|
1327 |
+
"""
|
1328 |
+
klass = kwargs.get('cls', kls)
|
1329 |
+
n = as_int(n)
|
1330 |
+
return klass._eval_wilkinson(n)
|
1331 |
+
|
1332 |
+
class MatrixProperties(MatrixRequired):
|
1333 |
+
"""Provides basic properties of a matrix."""
|
1334 |
+
|
1335 |
+
def _eval_atoms(self, *types):
|
1336 |
+
result = set()
|
1337 |
+
for i in self:
|
1338 |
+
result.update(i.atoms(*types))
|
1339 |
+
return result
|
1340 |
+
|
1341 |
+
def _eval_free_symbols(self):
|
1342 |
+
return set().union(*(i.free_symbols for i in self if i))
|
1343 |
+
|
1344 |
+
def _eval_has(self, *patterns):
|
1345 |
+
return any(a.has(*patterns) for a in self)
|
1346 |
+
|
1347 |
+
def _eval_is_anti_symmetric(self, simpfunc):
|
1348 |
+
if not all(simpfunc(self[i, j] + self[j, i]).is_zero for i in range(self.rows) for j in range(self.cols)):
|
1349 |
+
return False
|
1350 |
+
return True
|
1351 |
+
|
1352 |
+
def _eval_is_diagonal(self):
|
1353 |
+
for i in range(self.rows):
|
1354 |
+
for j in range(self.cols):
|
1355 |
+
if i != j and self[i, j]:
|
1356 |
+
return False
|
1357 |
+
return True
|
1358 |
+
|
1359 |
+
# _eval_is_hermitian is called by some general SymPy
|
1360 |
+
# routines and has a different *args signature. Make
|
1361 |
+
# sure the names don't clash by adding `_matrix_` in name.
|
1362 |
+
def _eval_is_matrix_hermitian(self, simpfunc):
|
1363 |
+
mat = self._new(self.rows, self.cols, lambda i, j: simpfunc(self[i, j] - self[j, i].conjugate()))
|
1364 |
+
return mat.is_zero_matrix
|
1365 |
+
|
1366 |
+
def _eval_is_Identity(self) -> FuzzyBool:
|
1367 |
+
def dirac(i, j):
|
1368 |
+
if i == j:
|
1369 |
+
return 1
|
1370 |
+
return 0
|
1371 |
+
|
1372 |
+
return all(self[i, j] == dirac(i, j)
|
1373 |
+
for i in range(self.rows)
|
1374 |
+
for j in range(self.cols))
|
1375 |
+
|
1376 |
+
def _eval_is_lower_hessenberg(self):
|
1377 |
+
return all(self[i, j].is_zero
|
1378 |
+
for i in range(self.rows)
|
1379 |
+
for j in range(i + 2, self.cols))
|
1380 |
+
|
1381 |
+
def _eval_is_lower(self):
|
1382 |
+
return all(self[i, j].is_zero
|
1383 |
+
for i in range(self.rows)
|
1384 |
+
for j in range(i + 1, self.cols))
|
1385 |
+
|
1386 |
+
def _eval_is_symbolic(self):
|
1387 |
+
return self.has(Symbol)
|
1388 |
+
|
1389 |
+
def _eval_is_symmetric(self, simpfunc):
|
1390 |
+
mat = self._new(self.rows, self.cols, lambda i, j: simpfunc(self[i, j] - self[j, i]))
|
1391 |
+
return mat.is_zero_matrix
|
1392 |
+
|
1393 |
+
def _eval_is_zero_matrix(self):
|
1394 |
+
if any(i.is_zero == False for i in self):
|
1395 |
+
return False
|
1396 |
+
if any(i.is_zero is None for i in self):
|
1397 |
+
return None
|
1398 |
+
return True
|
1399 |
+
|
1400 |
+
def _eval_is_upper_hessenberg(self):
|
1401 |
+
return all(self[i, j].is_zero
|
1402 |
+
for i in range(2, self.rows)
|
1403 |
+
for j in range(min(self.cols, (i - 1))))
|
1404 |
+
|
1405 |
+
def _eval_values(self):
|
1406 |
+
return [i for i in self if not i.is_zero]
|
1407 |
+
|
1408 |
+
def _has_positive_diagonals(self):
|
1409 |
+
diagonal_entries = (self[i, i] for i in range(self.rows))
|
1410 |
+
return fuzzy_and(x.is_positive for x in diagonal_entries)
|
1411 |
+
|
1412 |
+
def _has_nonnegative_diagonals(self):
|
1413 |
+
diagonal_entries = (self[i, i] for i in range(self.rows))
|
1414 |
+
return fuzzy_and(x.is_nonnegative for x in diagonal_entries)
|
1415 |
+
|
1416 |
+
def atoms(self, *types):
|
1417 |
+
"""Returns the atoms that form the current object.
|
1418 |
+
|
1419 |
+
Examples
|
1420 |
+
========
|
1421 |
+
|
1422 |
+
>>> from sympy.abc import x, y
|
1423 |
+
>>> from sympy import Matrix
|
1424 |
+
>>> Matrix([[x]])
|
1425 |
+
Matrix([[x]])
|
1426 |
+
>>> _.atoms()
|
1427 |
+
{x}
|
1428 |
+
>>> Matrix([[x, y], [y, x]])
|
1429 |
+
Matrix([
|
1430 |
+
[x, y],
|
1431 |
+
[y, x]])
|
1432 |
+
>>> _.atoms()
|
1433 |
+
{x, y}
|
1434 |
+
"""
|
1435 |
+
|
1436 |
+
types = tuple(t if isinstance(t, type) else type(t) for t in types)
|
1437 |
+
if not types:
|
1438 |
+
types = (Atom,)
|
1439 |
+
return self._eval_atoms(*types)
|
1440 |
+
|
1441 |
+
@property
|
1442 |
+
def free_symbols(self):
|
1443 |
+
"""Returns the free symbols within the matrix.
|
1444 |
+
|
1445 |
+
Examples
|
1446 |
+
========
|
1447 |
+
|
1448 |
+
>>> from sympy.abc import x
|
1449 |
+
>>> from sympy import Matrix
|
1450 |
+
>>> Matrix([[x], [1]]).free_symbols
|
1451 |
+
{x}
|
1452 |
+
"""
|
1453 |
+
return self._eval_free_symbols()
|
1454 |
+
|
1455 |
+
def has(self, *patterns):
|
1456 |
+
"""Test whether any subexpression matches any of the patterns.
|
1457 |
+
|
1458 |
+
Examples
|
1459 |
+
========
|
1460 |
+
|
1461 |
+
>>> from sympy import Matrix, SparseMatrix, Float
|
1462 |
+
>>> from sympy.abc import x, y
|
1463 |
+
>>> A = Matrix(((1, x), (0.2, 3)))
|
1464 |
+
>>> B = SparseMatrix(((1, x), (0.2, 3)))
|
1465 |
+
>>> A.has(x)
|
1466 |
+
True
|
1467 |
+
>>> A.has(y)
|
1468 |
+
False
|
1469 |
+
>>> A.has(Float)
|
1470 |
+
True
|
1471 |
+
>>> B.has(x)
|
1472 |
+
True
|
1473 |
+
>>> B.has(y)
|
1474 |
+
False
|
1475 |
+
>>> B.has(Float)
|
1476 |
+
True
|
1477 |
+
"""
|
1478 |
+
return self._eval_has(*patterns)
|
1479 |
+
|
1480 |
+
def is_anti_symmetric(self, simplify=True):
|
1481 |
+
"""Check if matrix M is an antisymmetric matrix,
|
1482 |
+
that is, M is a square matrix with all M[i, j] == -M[j, i].
|
1483 |
+
|
1484 |
+
When ``simplify=True`` (default), the sum M[i, j] + M[j, i] is
|
1485 |
+
simplified before testing to see if it is zero. By default,
|
1486 |
+
the SymPy simplify function is used. To use a custom function
|
1487 |
+
set simplify to a function that accepts a single argument which
|
1488 |
+
returns a simplified expression. To skip simplification, set
|
1489 |
+
simplify to False but note that although this will be faster,
|
1490 |
+
it may induce false negatives.
|
1491 |
+
|
1492 |
+
Examples
|
1493 |
+
========
|
1494 |
+
|
1495 |
+
>>> from sympy import Matrix, symbols
|
1496 |
+
>>> m = Matrix(2, 2, [0, 1, -1, 0])
|
1497 |
+
>>> m
|
1498 |
+
Matrix([
|
1499 |
+
[ 0, 1],
|
1500 |
+
[-1, 0]])
|
1501 |
+
>>> m.is_anti_symmetric()
|
1502 |
+
True
|
1503 |
+
>>> x, y = symbols('x y')
|
1504 |
+
>>> m = Matrix(2, 3, [0, 0, x, -y, 0, 0])
|
1505 |
+
>>> m
|
1506 |
+
Matrix([
|
1507 |
+
[ 0, 0, x],
|
1508 |
+
[-y, 0, 0]])
|
1509 |
+
>>> m.is_anti_symmetric()
|
1510 |
+
False
|
1511 |
+
|
1512 |
+
>>> from sympy.abc import x, y
|
1513 |
+
>>> m = Matrix(3, 3, [0, x**2 + 2*x + 1, y,
|
1514 |
+
... -(x + 1)**2, 0, x*y,
|
1515 |
+
... -y, -x*y, 0])
|
1516 |
+
|
1517 |
+
Simplification of matrix elements is done by default so even
|
1518 |
+
though two elements which should be equal and opposite would not
|
1519 |
+
pass an equality test, the matrix is still reported as
|
1520 |
+
anti-symmetric:
|
1521 |
+
|
1522 |
+
>>> m[0, 1] == -m[1, 0]
|
1523 |
+
False
|
1524 |
+
>>> m.is_anti_symmetric()
|
1525 |
+
True
|
1526 |
+
|
1527 |
+
If ``simplify=False`` is used for the case when a Matrix is already
|
1528 |
+
simplified, this will speed things up. Here, we see that without
|
1529 |
+
simplification the matrix does not appear anti-symmetric:
|
1530 |
+
|
1531 |
+
>>> print(m.is_anti_symmetric(simplify=False))
|
1532 |
+
None
|
1533 |
+
|
1534 |
+
But if the matrix were already expanded, then it would appear
|
1535 |
+
anti-symmetric and simplification in the is_anti_symmetric routine
|
1536 |
+
is not needed:
|
1537 |
+
|
1538 |
+
>>> m = m.expand()
|
1539 |
+
>>> m.is_anti_symmetric(simplify=False)
|
1540 |
+
True
|
1541 |
+
"""
|
1542 |
+
# accept custom simplification
|
1543 |
+
simpfunc = simplify
|
1544 |
+
if not isfunction(simplify):
|
1545 |
+
simpfunc = _simplify if simplify else lambda x: x
|
1546 |
+
|
1547 |
+
if not self.is_square:
|
1548 |
+
return False
|
1549 |
+
return self._eval_is_anti_symmetric(simpfunc)
|
1550 |
+
|
1551 |
+
def is_diagonal(self):
|
1552 |
+
"""Check if matrix is diagonal,
|
1553 |
+
that is matrix in which the entries outside the main diagonal are all zero.
|
1554 |
+
|
1555 |
+
Examples
|
1556 |
+
========
|
1557 |
+
|
1558 |
+
>>> from sympy import Matrix, diag
|
1559 |
+
>>> m = Matrix(2, 2, [1, 0, 0, 2])
|
1560 |
+
>>> m
|
1561 |
+
Matrix([
|
1562 |
+
[1, 0],
|
1563 |
+
[0, 2]])
|
1564 |
+
>>> m.is_diagonal()
|
1565 |
+
True
|
1566 |
+
|
1567 |
+
>>> m = Matrix(2, 2, [1, 1, 0, 2])
|
1568 |
+
>>> m
|
1569 |
+
Matrix([
|
1570 |
+
[1, 1],
|
1571 |
+
[0, 2]])
|
1572 |
+
>>> m.is_diagonal()
|
1573 |
+
False
|
1574 |
+
|
1575 |
+
>>> m = diag(1, 2, 3)
|
1576 |
+
>>> m
|
1577 |
+
Matrix([
|
1578 |
+
[1, 0, 0],
|
1579 |
+
[0, 2, 0],
|
1580 |
+
[0, 0, 3]])
|
1581 |
+
>>> m.is_diagonal()
|
1582 |
+
True
|
1583 |
+
|
1584 |
+
See Also
|
1585 |
+
========
|
1586 |
+
|
1587 |
+
is_lower
|
1588 |
+
is_upper
|
1589 |
+
sympy.matrices.matrixbase.MatrixCommon.is_diagonalizable
|
1590 |
+
diagonalize
|
1591 |
+
"""
|
1592 |
+
return self._eval_is_diagonal()
|
1593 |
+
|
1594 |
+
@property
|
1595 |
+
def is_weakly_diagonally_dominant(self):
|
1596 |
+
r"""Tests if the matrix is row weakly diagonally dominant.
|
1597 |
+
|
1598 |
+
Explanation
|
1599 |
+
===========
|
1600 |
+
|
1601 |
+
A $n, n$ matrix $A$ is row weakly diagonally dominant if
|
1602 |
+
|
1603 |
+
.. math::
|
1604 |
+
\left|A_{i, i}\right| \ge \sum_{j = 0, j \neq i}^{n-1}
|
1605 |
+
\left|A_{i, j}\right| \quad {\text{for all }}
|
1606 |
+
i \in \{ 0, ..., n-1 \}
|
1607 |
+
|
1608 |
+
Examples
|
1609 |
+
========
|
1610 |
+
|
1611 |
+
>>> from sympy import Matrix
|
1612 |
+
>>> A = Matrix([[3, -2, 1], [1, -3, 2], [-1, 2, 4]])
|
1613 |
+
>>> A.is_weakly_diagonally_dominant
|
1614 |
+
True
|
1615 |
+
|
1616 |
+
>>> A = Matrix([[-2, 2, 1], [1, 3, 2], [1, -2, 0]])
|
1617 |
+
>>> A.is_weakly_diagonally_dominant
|
1618 |
+
False
|
1619 |
+
|
1620 |
+
>>> A = Matrix([[-4, 2, 1], [1, 6, 2], [1, -2, 5]])
|
1621 |
+
>>> A.is_weakly_diagonally_dominant
|
1622 |
+
True
|
1623 |
+
|
1624 |
+
Notes
|
1625 |
+
=====
|
1626 |
+
|
1627 |
+
If you want to test whether a matrix is column diagonally
|
1628 |
+
dominant, you can apply the test after transposing the matrix.
|
1629 |
+
"""
|
1630 |
+
if not self.is_square:
|
1631 |
+
return False
|
1632 |
+
|
1633 |
+
rows, cols = self.shape
|
1634 |
+
|
1635 |
+
def test_row(i):
|
1636 |
+
summation = self.zero
|
1637 |
+
for j in range(cols):
|
1638 |
+
if i != j:
|
1639 |
+
summation += Abs(self[i, j])
|
1640 |
+
return (Abs(self[i, i]) - summation).is_nonnegative
|
1641 |
+
|
1642 |
+
return fuzzy_and(test_row(i) for i in range(rows))
|
1643 |
+
|
1644 |
+
@property
|
1645 |
+
def is_strongly_diagonally_dominant(self):
|
1646 |
+
r"""Tests if the matrix is row strongly diagonally dominant.
|
1647 |
+
|
1648 |
+
Explanation
|
1649 |
+
===========
|
1650 |
+
|
1651 |
+
A $n, n$ matrix $A$ is row strongly diagonally dominant if
|
1652 |
+
|
1653 |
+
.. math::
|
1654 |
+
\left|A_{i, i}\right| > \sum_{j = 0, j \neq i}^{n-1}
|
1655 |
+
\left|A_{i, j}\right| \quad {\text{for all }}
|
1656 |
+
i \in \{ 0, ..., n-1 \}
|
1657 |
+
|
1658 |
+
Examples
|
1659 |
+
========
|
1660 |
+
|
1661 |
+
>>> from sympy import Matrix
|
1662 |
+
>>> A = Matrix([[3, -2, 1], [1, -3, 2], [-1, 2, 4]])
|
1663 |
+
>>> A.is_strongly_diagonally_dominant
|
1664 |
+
False
|
1665 |
+
|
1666 |
+
>>> A = Matrix([[-2, 2, 1], [1, 3, 2], [1, -2, 0]])
|
1667 |
+
>>> A.is_strongly_diagonally_dominant
|
1668 |
+
False
|
1669 |
+
|
1670 |
+
>>> A = Matrix([[-4, 2, 1], [1, 6, 2], [1, -2, 5]])
|
1671 |
+
>>> A.is_strongly_diagonally_dominant
|
1672 |
+
True
|
1673 |
+
|
1674 |
+
Notes
|
1675 |
+
=====
|
1676 |
+
|
1677 |
+
If you want to test whether a matrix is column diagonally
|
1678 |
+
dominant, you can apply the test after transposing the matrix.
|
1679 |
+
"""
|
1680 |
+
if not self.is_square:
|
1681 |
+
return False
|
1682 |
+
|
1683 |
+
rows, cols = self.shape
|
1684 |
+
|
1685 |
+
def test_row(i):
|
1686 |
+
summation = self.zero
|
1687 |
+
for j in range(cols):
|
1688 |
+
if i != j:
|
1689 |
+
summation += Abs(self[i, j])
|
1690 |
+
return (Abs(self[i, i]) - summation).is_positive
|
1691 |
+
|
1692 |
+
return fuzzy_and(test_row(i) for i in range(rows))
|
1693 |
+
|
1694 |
+
@property
|
1695 |
+
def is_hermitian(self):
|
1696 |
+
"""Checks if the matrix is Hermitian.
|
1697 |
+
|
1698 |
+
In a Hermitian matrix element i,j is the complex conjugate of
|
1699 |
+
element j,i.
|
1700 |
+
|
1701 |
+
Examples
|
1702 |
+
========
|
1703 |
+
|
1704 |
+
>>> from sympy import Matrix
|
1705 |
+
>>> from sympy import I
|
1706 |
+
>>> from sympy.abc import x
|
1707 |
+
>>> a = Matrix([[1, I], [-I, 1]])
|
1708 |
+
>>> a
|
1709 |
+
Matrix([
|
1710 |
+
[ 1, I],
|
1711 |
+
[-I, 1]])
|
1712 |
+
>>> a.is_hermitian
|
1713 |
+
True
|
1714 |
+
>>> a[0, 0] = 2*I
|
1715 |
+
>>> a.is_hermitian
|
1716 |
+
False
|
1717 |
+
>>> a[0, 0] = x
|
1718 |
+
>>> a.is_hermitian
|
1719 |
+
>>> a[0, 1] = a[1, 0]*I
|
1720 |
+
>>> a.is_hermitian
|
1721 |
+
False
|
1722 |
+
"""
|
1723 |
+
if not self.is_square:
|
1724 |
+
return False
|
1725 |
+
|
1726 |
+
return self._eval_is_matrix_hermitian(_simplify)
|
1727 |
+
|
1728 |
+
@property
|
1729 |
+
def is_Identity(self) -> FuzzyBool:
|
1730 |
+
if not self.is_square:
|
1731 |
+
return False
|
1732 |
+
return self._eval_is_Identity()
|
1733 |
+
|
1734 |
+
@property
|
1735 |
+
def is_lower_hessenberg(self):
|
1736 |
+
r"""Checks if the matrix is in the lower-Hessenberg form.
|
1737 |
+
|
1738 |
+
The lower hessenberg matrix has zero entries
|
1739 |
+
above the first superdiagonal.
|
1740 |
+
|
1741 |
+
Examples
|
1742 |
+
========
|
1743 |
+
|
1744 |
+
>>> from sympy import Matrix
|
1745 |
+
>>> a = Matrix([[1, 2, 0, 0], [5, 2, 3, 0], [3, 4, 3, 7], [5, 6, 1, 1]])
|
1746 |
+
>>> a
|
1747 |
+
Matrix([
|
1748 |
+
[1, 2, 0, 0],
|
1749 |
+
[5, 2, 3, 0],
|
1750 |
+
[3, 4, 3, 7],
|
1751 |
+
[5, 6, 1, 1]])
|
1752 |
+
>>> a.is_lower_hessenberg
|
1753 |
+
True
|
1754 |
+
|
1755 |
+
See Also
|
1756 |
+
========
|
1757 |
+
|
1758 |
+
is_upper_hessenberg
|
1759 |
+
is_lower
|
1760 |
+
"""
|
1761 |
+
return self._eval_is_lower_hessenberg()
|
1762 |
+
|
1763 |
+
@property
|
1764 |
+
def is_lower(self):
|
1765 |
+
"""Check if matrix is a lower triangular matrix. True can be returned
|
1766 |
+
even if the matrix is not square.
|
1767 |
+
|
1768 |
+
Examples
|
1769 |
+
========
|
1770 |
+
|
1771 |
+
>>> from sympy import Matrix
|
1772 |
+
>>> m = Matrix(2, 2, [1, 0, 0, 1])
|
1773 |
+
>>> m
|
1774 |
+
Matrix([
|
1775 |
+
[1, 0],
|
1776 |
+
[0, 1]])
|
1777 |
+
>>> m.is_lower
|
1778 |
+
True
|
1779 |
+
|
1780 |
+
>>> m = Matrix(4, 3, [0, 0, 0, 2, 0, 0, 1, 4, 0, 6, 6, 5])
|
1781 |
+
>>> m
|
1782 |
+
Matrix([
|
1783 |
+
[0, 0, 0],
|
1784 |
+
[2, 0, 0],
|
1785 |
+
[1, 4, 0],
|
1786 |
+
[6, 6, 5]])
|
1787 |
+
>>> m.is_lower
|
1788 |
+
True
|
1789 |
+
|
1790 |
+
>>> from sympy.abc import x, y
|
1791 |
+
>>> m = Matrix(2, 2, [x**2 + y, y**2 + x, 0, x + y])
|
1792 |
+
>>> m
|
1793 |
+
Matrix([
|
1794 |
+
[x**2 + y, x + y**2],
|
1795 |
+
[ 0, x + y]])
|
1796 |
+
>>> m.is_lower
|
1797 |
+
False
|
1798 |
+
|
1799 |
+
See Also
|
1800 |
+
========
|
1801 |
+
|
1802 |
+
is_upper
|
1803 |
+
is_diagonal
|
1804 |
+
is_lower_hessenberg
|
1805 |
+
"""
|
1806 |
+
return self._eval_is_lower()
|
1807 |
+
|
1808 |
+
@property
|
1809 |
+
def is_square(self):
|
1810 |
+
"""Checks if a matrix is square.
|
1811 |
+
|
1812 |
+
A matrix is square if the number of rows equals the number of columns.
|
1813 |
+
The empty matrix is square by definition, since the number of rows and
|
1814 |
+
the number of columns are both zero.
|
1815 |
+
|
1816 |
+
Examples
|
1817 |
+
========
|
1818 |
+
|
1819 |
+
>>> from sympy import Matrix
|
1820 |
+
>>> a = Matrix([[1, 2, 3], [4, 5, 6]])
|
1821 |
+
>>> b = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
|
1822 |
+
>>> c = Matrix([])
|
1823 |
+
>>> a.is_square
|
1824 |
+
False
|
1825 |
+
>>> b.is_square
|
1826 |
+
True
|
1827 |
+
>>> c.is_square
|
1828 |
+
True
|
1829 |
+
"""
|
1830 |
+
return self.rows == self.cols
|
1831 |
+
|
1832 |
+
def is_symbolic(self):
|
1833 |
+
"""Checks if any elements contain Symbols.
|
1834 |
+
|
1835 |
+
Examples
|
1836 |
+
========
|
1837 |
+
|
1838 |
+
>>> from sympy import Matrix
|
1839 |
+
>>> from sympy.abc import x, y
|
1840 |
+
>>> M = Matrix([[x, y], [1, 0]])
|
1841 |
+
>>> M.is_symbolic()
|
1842 |
+
True
|
1843 |
+
|
1844 |
+
"""
|
1845 |
+
return self._eval_is_symbolic()
|
1846 |
+
|
1847 |
+
def is_symmetric(self, simplify=True):
|
1848 |
+
"""Check if matrix is symmetric matrix,
|
1849 |
+
that is square matrix and is equal to its transpose.
|
1850 |
+
|
1851 |
+
By default, simplifications occur before testing symmetry.
|
1852 |
+
They can be skipped using 'simplify=False'; while speeding things a bit,
|
1853 |
+
this may however induce false negatives.
|
1854 |
+
|
1855 |
+
Examples
|
1856 |
+
========
|
1857 |
+
|
1858 |
+
>>> from sympy import Matrix
|
1859 |
+
>>> m = Matrix(2, 2, [0, 1, 1, 2])
|
1860 |
+
>>> m
|
1861 |
+
Matrix([
|
1862 |
+
[0, 1],
|
1863 |
+
[1, 2]])
|
1864 |
+
>>> m.is_symmetric()
|
1865 |
+
True
|
1866 |
+
|
1867 |
+
>>> m = Matrix(2, 2, [0, 1, 2, 0])
|
1868 |
+
>>> m
|
1869 |
+
Matrix([
|
1870 |
+
[0, 1],
|
1871 |
+
[2, 0]])
|
1872 |
+
>>> m.is_symmetric()
|
1873 |
+
False
|
1874 |
+
|
1875 |
+
>>> m = Matrix(2, 3, [0, 0, 0, 0, 0, 0])
|
1876 |
+
>>> m
|
1877 |
+
Matrix([
|
1878 |
+
[0, 0, 0],
|
1879 |
+
[0, 0, 0]])
|
1880 |
+
>>> m.is_symmetric()
|
1881 |
+
False
|
1882 |
+
|
1883 |
+
>>> from sympy.abc import x, y
|
1884 |
+
>>> m = Matrix(3, 3, [1, x**2 + 2*x + 1, y, (x + 1)**2, 2, 0, y, 0, 3])
|
1885 |
+
>>> m
|
1886 |
+
Matrix([
|
1887 |
+
[ 1, x**2 + 2*x + 1, y],
|
1888 |
+
[(x + 1)**2, 2, 0],
|
1889 |
+
[ y, 0, 3]])
|
1890 |
+
>>> m.is_symmetric()
|
1891 |
+
True
|
1892 |
+
|
1893 |
+
If the matrix is already simplified, you may speed-up is_symmetric()
|
1894 |
+
test by using 'simplify=False'.
|
1895 |
+
|
1896 |
+
>>> bool(m.is_symmetric(simplify=False))
|
1897 |
+
False
|
1898 |
+
>>> m1 = m.expand()
|
1899 |
+
>>> m1.is_symmetric(simplify=False)
|
1900 |
+
True
|
1901 |
+
"""
|
1902 |
+
simpfunc = simplify
|
1903 |
+
if not isfunction(simplify):
|
1904 |
+
simpfunc = _simplify if simplify else lambda x: x
|
1905 |
+
|
1906 |
+
if not self.is_square:
|
1907 |
+
return False
|
1908 |
+
|
1909 |
+
return self._eval_is_symmetric(simpfunc)
|
1910 |
+
|
1911 |
+
@property
|
1912 |
+
def is_upper_hessenberg(self):
|
1913 |
+
"""Checks if the matrix is the upper-Hessenberg form.
|
1914 |
+
|
1915 |
+
The upper hessenberg matrix has zero entries
|
1916 |
+
below the first subdiagonal.
|
1917 |
+
|
1918 |
+
Examples
|
1919 |
+
========
|
1920 |
+
|
1921 |
+
>>> from sympy import Matrix
|
1922 |
+
>>> a = Matrix([[1, 4, 2, 3], [3, 4, 1, 7], [0, 2, 3, 4], [0, 0, 1, 3]])
|
1923 |
+
>>> a
|
1924 |
+
Matrix([
|
1925 |
+
[1, 4, 2, 3],
|
1926 |
+
[3, 4, 1, 7],
|
1927 |
+
[0, 2, 3, 4],
|
1928 |
+
[0, 0, 1, 3]])
|
1929 |
+
>>> a.is_upper_hessenberg
|
1930 |
+
True
|
1931 |
+
|
1932 |
+
See Also
|
1933 |
+
========
|
1934 |
+
|
1935 |
+
is_lower_hessenberg
|
1936 |
+
is_upper
|
1937 |
+
"""
|
1938 |
+
return self._eval_is_upper_hessenberg()
|
1939 |
+
|
1940 |
+
@property
|
1941 |
+
def is_upper(self):
|
1942 |
+
"""Check if matrix is an upper triangular matrix. True can be returned
|
1943 |
+
even if the matrix is not square.
|
1944 |
+
|
1945 |
+
Examples
|
1946 |
+
========
|
1947 |
+
|
1948 |
+
>>> from sympy import Matrix
|
1949 |
+
>>> m = Matrix(2, 2, [1, 0, 0, 1])
|
1950 |
+
>>> m
|
1951 |
+
Matrix([
|
1952 |
+
[1, 0],
|
1953 |
+
[0, 1]])
|
1954 |
+
>>> m.is_upper
|
1955 |
+
True
|
1956 |
+
|
1957 |
+
>>> m = Matrix(4, 3, [5, 1, 9, 0, 4, 6, 0, 0, 5, 0, 0, 0])
|
1958 |
+
>>> m
|
1959 |
+
Matrix([
|
1960 |
+
[5, 1, 9],
|
1961 |
+
[0, 4, 6],
|
1962 |
+
[0, 0, 5],
|
1963 |
+
[0, 0, 0]])
|
1964 |
+
>>> m.is_upper
|
1965 |
+
True
|
1966 |
+
|
1967 |
+
>>> m = Matrix(2, 3, [4, 2, 5, 6, 1, 1])
|
1968 |
+
>>> m
|
1969 |
+
Matrix([
|
1970 |
+
[4, 2, 5],
|
1971 |
+
[6, 1, 1]])
|
1972 |
+
>>> m.is_upper
|
1973 |
+
False
|
1974 |
+
|
1975 |
+
See Also
|
1976 |
+
========
|
1977 |
+
|
1978 |
+
is_lower
|
1979 |
+
is_diagonal
|
1980 |
+
is_upper_hessenberg
|
1981 |
+
"""
|
1982 |
+
return all(self[i, j].is_zero
|
1983 |
+
for i in range(1, self.rows)
|
1984 |
+
for j in range(min(i, self.cols)))
|
1985 |
+
|
1986 |
+
@property
|
1987 |
+
def is_zero_matrix(self):
|
1988 |
+
"""Checks if a matrix is a zero matrix.
|
1989 |
+
|
1990 |
+
A matrix is zero if every element is zero. A matrix need not be square
|
1991 |
+
to be considered zero. The empty matrix is zero by the principle of
|
1992 |
+
vacuous truth. For a matrix that may or may not be zero (e.g.
|
1993 |
+
contains a symbol), this will be None
|
1994 |
+
|
1995 |
+
Examples
|
1996 |
+
========
|
1997 |
+
|
1998 |
+
>>> from sympy import Matrix, zeros
|
1999 |
+
>>> from sympy.abc import x
|
2000 |
+
>>> a = Matrix([[0, 0], [0, 0]])
|
2001 |
+
>>> b = zeros(3, 4)
|
2002 |
+
>>> c = Matrix([[0, 1], [0, 0]])
|
2003 |
+
>>> d = Matrix([])
|
2004 |
+
>>> e = Matrix([[x, 0], [0, 0]])
|
2005 |
+
>>> a.is_zero_matrix
|
2006 |
+
True
|
2007 |
+
>>> b.is_zero_matrix
|
2008 |
+
True
|
2009 |
+
>>> c.is_zero_matrix
|
2010 |
+
False
|
2011 |
+
>>> d.is_zero_matrix
|
2012 |
+
True
|
2013 |
+
>>> e.is_zero_matrix
|
2014 |
+
"""
|
2015 |
+
return self._eval_is_zero_matrix()
|
2016 |
+
|
2017 |
+
def values(self):
|
2018 |
+
"""Return non-zero values of self."""
|
2019 |
+
return self._eval_values()
|
2020 |
+
|
2021 |
+
|
2022 |
+
class MatrixOperations(MatrixRequired):
|
2023 |
+
"""Provides basic matrix shape and elementwise
|
2024 |
+
operations. Should not be instantiated directly."""
|
2025 |
+
|
2026 |
+
def _eval_adjoint(self):
|
2027 |
+
return self.transpose().conjugate()
|
2028 |
+
|
2029 |
+
def _eval_applyfunc(self, f):
|
2030 |
+
out = self._new(self.rows, self.cols, [f(x) for x in self])
|
2031 |
+
return out
|
2032 |
+
|
2033 |
+
def _eval_as_real_imag(self): # type: ignore
|
2034 |
+
return (self.applyfunc(re), self.applyfunc(im))
|
2035 |
+
|
2036 |
+
def _eval_conjugate(self):
|
2037 |
+
return self.applyfunc(lambda x: x.conjugate())
|
2038 |
+
|
2039 |
+
def _eval_permute_cols(self, perm):
|
2040 |
+
# apply the permutation to a list
|
2041 |
+
mapping = list(perm)
|
2042 |
+
|
2043 |
+
def entry(i, j):
|
2044 |
+
return self[i, mapping[j]]
|
2045 |
+
|
2046 |
+
return self._new(self.rows, self.cols, entry)
|
2047 |
+
|
2048 |
+
def _eval_permute_rows(self, perm):
|
2049 |
+
# apply the permutation to a list
|
2050 |
+
mapping = list(perm)
|
2051 |
+
|
2052 |
+
def entry(i, j):
|
2053 |
+
return self[mapping[i], j]
|
2054 |
+
|
2055 |
+
return self._new(self.rows, self.cols, entry)
|
2056 |
+
|
2057 |
+
def _eval_trace(self):
|
2058 |
+
return sum(self[i, i] for i in range(self.rows))
|
2059 |
+
|
2060 |
+
def _eval_transpose(self):
|
2061 |
+
return self._new(self.cols, self.rows, lambda i, j: self[j, i])
|
2062 |
+
|
2063 |
+
def adjoint(self):
|
2064 |
+
"""Conjugate transpose or Hermitian conjugation."""
|
2065 |
+
return self._eval_adjoint()
|
2066 |
+
|
2067 |
+
def applyfunc(self, f):
|
2068 |
+
"""Apply a function to each element of the matrix.
|
2069 |
+
|
2070 |
+
Examples
|
2071 |
+
========
|
2072 |
+
|
2073 |
+
>>> from sympy import Matrix
|
2074 |
+
>>> m = Matrix(2, 2, lambda i, j: i*2+j)
|
2075 |
+
>>> m
|
2076 |
+
Matrix([
|
2077 |
+
[0, 1],
|
2078 |
+
[2, 3]])
|
2079 |
+
>>> m.applyfunc(lambda i: 2*i)
|
2080 |
+
Matrix([
|
2081 |
+
[0, 2],
|
2082 |
+
[4, 6]])
|
2083 |
+
|
2084 |
+
"""
|
2085 |
+
if not callable(f):
|
2086 |
+
raise TypeError("`f` must be callable.")
|
2087 |
+
|
2088 |
+
return self._eval_applyfunc(f)
|
2089 |
+
|
2090 |
+
def as_real_imag(self, deep=True, **hints):
|
2091 |
+
"""Returns a tuple containing the (real, imaginary) part of matrix."""
|
2092 |
+
# XXX: Ignoring deep and hints...
|
2093 |
+
return self._eval_as_real_imag()
|
2094 |
+
|
2095 |
+
def conjugate(self):
|
2096 |
+
"""Return the by-element conjugation.
|
2097 |
+
|
2098 |
+
Examples
|
2099 |
+
========
|
2100 |
+
|
2101 |
+
>>> from sympy import SparseMatrix, I
|
2102 |
+
>>> a = SparseMatrix(((1, 2 + I), (3, 4), (I, -I)))
|
2103 |
+
>>> a
|
2104 |
+
Matrix([
|
2105 |
+
[1, 2 + I],
|
2106 |
+
[3, 4],
|
2107 |
+
[I, -I]])
|
2108 |
+
>>> a.C
|
2109 |
+
Matrix([
|
2110 |
+
[ 1, 2 - I],
|
2111 |
+
[ 3, 4],
|
2112 |
+
[-I, I]])
|
2113 |
+
|
2114 |
+
See Also
|
2115 |
+
========
|
2116 |
+
|
2117 |
+
transpose: Matrix transposition
|
2118 |
+
H: Hermite conjugation
|
2119 |
+
sympy.matrices.matrixbase.MatrixBase.D: Dirac conjugation
|
2120 |
+
"""
|
2121 |
+
return self._eval_conjugate()
|
2122 |
+
|
2123 |
+
def doit(self, **hints):
|
2124 |
+
return self.applyfunc(lambda x: x.doit(**hints))
|
2125 |
+
|
2126 |
+
def evalf(self, n=15, subs=None, maxn=100, chop=False, strict=False, quad=None, verbose=False):
|
2127 |
+
"""Apply evalf() to each element of self."""
|
2128 |
+
options = {'subs':subs, 'maxn':maxn, 'chop':chop, 'strict':strict,
|
2129 |
+
'quad':quad, 'verbose':verbose}
|
2130 |
+
return self.applyfunc(lambda i: i.evalf(n, **options))
|
2131 |
+
|
2132 |
+
def expand(self, deep=True, modulus=None, power_base=True, power_exp=True,
|
2133 |
+
mul=True, log=True, multinomial=True, basic=True, **hints):
|
2134 |
+
"""Apply core.function.expand to each entry of the matrix.
|
2135 |
+
|
2136 |
+
Examples
|
2137 |
+
========
|
2138 |
+
|
2139 |
+
>>> from sympy.abc import x
|
2140 |
+
>>> from sympy import Matrix
|
2141 |
+
>>> Matrix(1, 1, [x*(x+1)])
|
2142 |
+
Matrix([[x*(x + 1)]])
|
2143 |
+
>>> _.expand()
|
2144 |
+
Matrix([[x**2 + x]])
|
2145 |
+
|
2146 |
+
"""
|
2147 |
+
return self.applyfunc(lambda x: x.expand(
|
2148 |
+
deep, modulus, power_base, power_exp, mul, log, multinomial, basic,
|
2149 |
+
**hints))
|
2150 |
+
|
2151 |
+
@property
|
2152 |
+
def H(self):
|
2153 |
+
"""Return Hermite conjugate.
|
2154 |
+
|
2155 |
+
Examples
|
2156 |
+
========
|
2157 |
+
|
2158 |
+
>>> from sympy import Matrix, I
|
2159 |
+
>>> m = Matrix((0, 1 + I, 2, 3))
|
2160 |
+
>>> m
|
2161 |
+
Matrix([
|
2162 |
+
[ 0],
|
2163 |
+
[1 + I],
|
2164 |
+
[ 2],
|
2165 |
+
[ 3]])
|
2166 |
+
>>> m.H
|
2167 |
+
Matrix([[0, 1 - I, 2, 3]])
|
2168 |
+
|
2169 |
+
See Also
|
2170 |
+
========
|
2171 |
+
|
2172 |
+
conjugate: By-element conjugation
|
2173 |
+
sympy.matrices.matrixbase.MatrixBase.D: Dirac conjugation
|
2174 |
+
"""
|
2175 |
+
return self.T.C
|
2176 |
+
|
2177 |
+
def permute(self, perm, orientation='rows', direction='forward'):
|
2178 |
+
r"""Permute the rows or columns of a matrix by the given list of
|
2179 |
+
swaps.
|
2180 |
+
|
2181 |
+
Parameters
|
2182 |
+
==========
|
2183 |
+
|
2184 |
+
perm : Permutation, list, or list of lists
|
2185 |
+
A representation for the permutation.
|
2186 |
+
|
2187 |
+
If it is ``Permutation``, it is used directly with some
|
2188 |
+
resizing with respect to the matrix size.
|
2189 |
+
|
2190 |
+
If it is specified as list of lists,
|
2191 |
+
(e.g., ``[[0, 1], [0, 2]]``), then the permutation is formed
|
2192 |
+
from applying the product of cycles. The direction how the
|
2193 |
+
cyclic product is applied is described in below.
|
2194 |
+
|
2195 |
+
If it is specified as a list, the list should represent
|
2196 |
+
an array form of a permutation. (e.g., ``[1, 2, 0]``) which
|
2197 |
+
would would form the swapping function
|
2198 |
+
`0 \mapsto 1, 1 \mapsto 2, 2\mapsto 0`.
|
2199 |
+
|
2200 |
+
orientation : 'rows', 'cols'
|
2201 |
+
A flag to control whether to permute the rows or the columns
|
2202 |
+
|
2203 |
+
direction : 'forward', 'backward'
|
2204 |
+
A flag to control whether to apply the permutations from
|
2205 |
+
the start of the list first, or from the back of the list
|
2206 |
+
first.
|
2207 |
+
|
2208 |
+
For example, if the permutation specification is
|
2209 |
+
``[[0, 1], [0, 2]]``,
|
2210 |
+
|
2211 |
+
If the flag is set to ``'forward'``, the cycle would be
|
2212 |
+
formed as `0 \mapsto 2, 2 \mapsto 1, 1 \mapsto 0`.
|
2213 |
+
|
2214 |
+
If the flag is set to ``'backward'``, the cycle would be
|
2215 |
+
formed as `0 \mapsto 1, 1 \mapsto 2, 2 \mapsto 0`.
|
2216 |
+
|
2217 |
+
If the argument ``perm`` is not in a form of list of lists,
|
2218 |
+
this flag takes no effect.
|
2219 |
+
|
2220 |
+
Examples
|
2221 |
+
========
|
2222 |
+
|
2223 |
+
>>> from sympy import eye
|
2224 |
+
>>> M = eye(3)
|
2225 |
+
>>> M.permute([[0, 1], [0, 2]], orientation='rows', direction='forward')
|
2226 |
+
Matrix([
|
2227 |
+
[0, 0, 1],
|
2228 |
+
[1, 0, 0],
|
2229 |
+
[0, 1, 0]])
|
2230 |
+
|
2231 |
+
>>> from sympy import eye
|
2232 |
+
>>> M = eye(3)
|
2233 |
+
>>> M.permute([[0, 1], [0, 2]], orientation='rows', direction='backward')
|
2234 |
+
Matrix([
|
2235 |
+
[0, 1, 0],
|
2236 |
+
[0, 0, 1],
|
2237 |
+
[1, 0, 0]])
|
2238 |
+
|
2239 |
+
Notes
|
2240 |
+
=====
|
2241 |
+
|
2242 |
+
If a bijective function
|
2243 |
+
`\sigma : \mathbb{N}_0 \rightarrow \mathbb{N}_0` denotes the
|
2244 |
+
permutation.
|
2245 |
+
|
2246 |
+
If the matrix `A` is the matrix to permute, represented as
|
2247 |
+
a horizontal or a vertical stack of vectors:
|
2248 |
+
|
2249 |
+
.. math::
|
2250 |
+
A =
|
2251 |
+
\begin{bmatrix}
|
2252 |
+
a_0 \\ a_1 \\ \vdots \\ a_{n-1}
|
2253 |
+
\end{bmatrix} =
|
2254 |
+
\begin{bmatrix}
|
2255 |
+
\alpha_0 & \alpha_1 & \cdots & \alpha_{n-1}
|
2256 |
+
\end{bmatrix}
|
2257 |
+
|
2258 |
+
If the matrix `B` is the result, the permutation of matrix rows
|
2259 |
+
is defined as:
|
2260 |
+
|
2261 |
+
.. math::
|
2262 |
+
B := \begin{bmatrix}
|
2263 |
+
a_{\sigma(0)} \\ a_{\sigma(1)} \\ \vdots \\ a_{\sigma(n-1)}
|
2264 |
+
\end{bmatrix}
|
2265 |
+
|
2266 |
+
And the permutation of matrix columns is defined as:
|
2267 |
+
|
2268 |
+
.. math::
|
2269 |
+
B := \begin{bmatrix}
|
2270 |
+
\alpha_{\sigma(0)} & \alpha_{\sigma(1)} &
|
2271 |
+
\cdots & \alpha_{\sigma(n-1)}
|
2272 |
+
\end{bmatrix}
|
2273 |
+
"""
|
2274 |
+
from sympy.combinatorics import Permutation
|
2275 |
+
|
2276 |
+
# allow british variants and `columns`
|
2277 |
+
if direction == 'forwards':
|
2278 |
+
direction = 'forward'
|
2279 |
+
if direction == 'backwards':
|
2280 |
+
direction = 'backward'
|
2281 |
+
if orientation == 'columns':
|
2282 |
+
orientation = 'cols'
|
2283 |
+
|
2284 |
+
if direction not in ('forward', 'backward'):
|
2285 |
+
raise TypeError("direction='{}' is an invalid kwarg. "
|
2286 |
+
"Try 'forward' or 'backward'".format(direction))
|
2287 |
+
if orientation not in ('rows', 'cols'):
|
2288 |
+
raise TypeError("orientation='{}' is an invalid kwarg. "
|
2289 |
+
"Try 'rows' or 'cols'".format(orientation))
|
2290 |
+
|
2291 |
+
if not isinstance(perm, (Permutation, Iterable)):
|
2292 |
+
raise ValueError(
|
2293 |
+
"{} must be a list, a list of lists, "
|
2294 |
+
"or a SymPy permutation object.".format(perm))
|
2295 |
+
|
2296 |
+
# ensure all swaps are in range
|
2297 |
+
max_index = self.rows if orientation == 'rows' else self.cols
|
2298 |
+
if not all(0 <= t <= max_index for t in flatten(list(perm))):
|
2299 |
+
raise IndexError("`swap` indices out of range.")
|
2300 |
+
|
2301 |
+
if perm and not isinstance(perm, Permutation) and \
|
2302 |
+
isinstance(perm[0], Iterable):
|
2303 |
+
if direction == 'forward':
|
2304 |
+
perm = list(reversed(perm))
|
2305 |
+
perm = Permutation(perm, size=max_index+1)
|
2306 |
+
else:
|
2307 |
+
perm = Permutation(perm, size=max_index+1)
|
2308 |
+
|
2309 |
+
if orientation == 'rows':
|
2310 |
+
return self._eval_permute_rows(perm)
|
2311 |
+
if orientation == 'cols':
|
2312 |
+
return self._eval_permute_cols(perm)
|
2313 |
+
|
2314 |
+
def permute_cols(self, swaps, direction='forward'):
|
2315 |
+
"""Alias for
|
2316 |
+
``self.permute(swaps, orientation='cols', direction=direction)``
|
2317 |
+
|
2318 |
+
See Also
|
2319 |
+
========
|
2320 |
+
|
2321 |
+
permute
|
2322 |
+
"""
|
2323 |
+
return self.permute(swaps, orientation='cols', direction=direction)
|
2324 |
+
|
2325 |
+
def permute_rows(self, swaps, direction='forward'):
|
2326 |
+
"""Alias for
|
2327 |
+
``self.permute(swaps, orientation='rows', direction=direction)``
|
2328 |
+
|
2329 |
+
See Also
|
2330 |
+
========
|
2331 |
+
|
2332 |
+
permute
|
2333 |
+
"""
|
2334 |
+
return self.permute(swaps, orientation='rows', direction=direction)
|
2335 |
+
|
2336 |
+
def refine(self, assumptions=True):
|
2337 |
+
"""Apply refine to each element of the matrix.
|
2338 |
+
|
2339 |
+
Examples
|
2340 |
+
========
|
2341 |
+
|
2342 |
+
>>> from sympy import Symbol, Matrix, Abs, sqrt, Q
|
2343 |
+
>>> x = Symbol('x')
|
2344 |
+
>>> Matrix([[Abs(x)**2, sqrt(x**2)],[sqrt(x**2), Abs(x)**2]])
|
2345 |
+
Matrix([
|
2346 |
+
[ Abs(x)**2, sqrt(x**2)],
|
2347 |
+
[sqrt(x**2), Abs(x)**2]])
|
2348 |
+
>>> _.refine(Q.real(x))
|
2349 |
+
Matrix([
|
2350 |
+
[ x**2, Abs(x)],
|
2351 |
+
[Abs(x), x**2]])
|
2352 |
+
|
2353 |
+
"""
|
2354 |
+
return self.applyfunc(lambda x: refine(x, assumptions))
|
2355 |
+
|
2356 |
+
def replace(self, F, G, map=False, simultaneous=True, exact=None):
|
2357 |
+
"""Replaces Function F in Matrix entries with Function G.
|
2358 |
+
|
2359 |
+
Examples
|
2360 |
+
========
|
2361 |
+
|
2362 |
+
>>> from sympy import symbols, Function, Matrix
|
2363 |
+
>>> F, G = symbols('F, G', cls=Function)
|
2364 |
+
>>> M = Matrix(2, 2, lambda i, j: F(i+j)) ; M
|
2365 |
+
Matrix([
|
2366 |
+
[F(0), F(1)],
|
2367 |
+
[F(1), F(2)]])
|
2368 |
+
>>> N = M.replace(F,G)
|
2369 |
+
>>> N
|
2370 |
+
Matrix([
|
2371 |
+
[G(0), G(1)],
|
2372 |
+
[G(1), G(2)]])
|
2373 |
+
"""
|
2374 |
+
return self.applyfunc(
|
2375 |
+
lambda x: x.replace(F, G, map=map, simultaneous=simultaneous, exact=exact))
|
2376 |
+
|
2377 |
+
def rot90(self, k=1):
|
2378 |
+
"""Rotates Matrix by 90 degrees
|
2379 |
+
|
2380 |
+
Parameters
|
2381 |
+
==========
|
2382 |
+
|
2383 |
+
k : int
|
2384 |
+
Specifies how many times the matrix is rotated by 90 degrees
|
2385 |
+
(clockwise when positive, counter-clockwise when negative).
|
2386 |
+
|
2387 |
+
Examples
|
2388 |
+
========
|
2389 |
+
|
2390 |
+
>>> from sympy import Matrix, symbols
|
2391 |
+
>>> A = Matrix(2, 2, symbols('a:d'))
|
2392 |
+
>>> A
|
2393 |
+
Matrix([
|
2394 |
+
[a, b],
|
2395 |
+
[c, d]])
|
2396 |
+
|
2397 |
+
Rotating the matrix clockwise one time:
|
2398 |
+
|
2399 |
+
>>> A.rot90(1)
|
2400 |
+
Matrix([
|
2401 |
+
[c, a],
|
2402 |
+
[d, b]])
|
2403 |
+
|
2404 |
+
Rotating the matrix anticlockwise two times:
|
2405 |
+
|
2406 |
+
>>> A.rot90(-2)
|
2407 |
+
Matrix([
|
2408 |
+
[d, c],
|
2409 |
+
[b, a]])
|
2410 |
+
"""
|
2411 |
+
|
2412 |
+
mod = k%4
|
2413 |
+
if mod == 0:
|
2414 |
+
return self
|
2415 |
+
if mod == 1:
|
2416 |
+
return self[::-1, ::].T
|
2417 |
+
if mod == 2:
|
2418 |
+
return self[::-1, ::-1]
|
2419 |
+
if mod == 3:
|
2420 |
+
return self[::, ::-1].T
|
2421 |
+
|
2422 |
+
def simplify(self, **kwargs):
|
2423 |
+
"""Apply simplify to each element of the matrix.
|
2424 |
+
|
2425 |
+
Examples
|
2426 |
+
========
|
2427 |
+
|
2428 |
+
>>> from sympy.abc import x, y
|
2429 |
+
>>> from sympy import SparseMatrix, sin, cos
|
2430 |
+
>>> SparseMatrix(1, 1, [x*sin(y)**2 + x*cos(y)**2])
|
2431 |
+
Matrix([[x*sin(y)**2 + x*cos(y)**2]])
|
2432 |
+
>>> _.simplify()
|
2433 |
+
Matrix([[x]])
|
2434 |
+
"""
|
2435 |
+
return self.applyfunc(lambda x: x.simplify(**kwargs))
|
2436 |
+
|
2437 |
+
def subs(self, *args, **kwargs): # should mirror core.basic.subs
|
2438 |
+
"""Return a new matrix with subs applied to each entry.
|
2439 |
+
|
2440 |
+
Examples
|
2441 |
+
========
|
2442 |
+
|
2443 |
+
>>> from sympy.abc import x, y
|
2444 |
+
>>> from sympy import SparseMatrix, Matrix
|
2445 |
+
>>> SparseMatrix(1, 1, [x])
|
2446 |
+
Matrix([[x]])
|
2447 |
+
>>> _.subs(x, y)
|
2448 |
+
Matrix([[y]])
|
2449 |
+
>>> Matrix(_).subs(y, x)
|
2450 |
+
Matrix([[x]])
|
2451 |
+
"""
|
2452 |
+
|
2453 |
+
if len(args) == 1 and not isinstance(args[0], (dict, set)) and iter(args[0]) and not is_sequence(args[0]):
|
2454 |
+
args = (list(args[0]),)
|
2455 |
+
|
2456 |
+
return self.applyfunc(lambda x: x.subs(*args, **kwargs))
|
2457 |
+
|
2458 |
+
def trace(self):
|
2459 |
+
"""
|
2460 |
+
Returns the trace of a square matrix i.e. the sum of the
|
2461 |
+
diagonal elements.
|
2462 |
+
|
2463 |
+
Examples
|
2464 |
+
========
|
2465 |
+
|
2466 |
+
>>> from sympy import Matrix
|
2467 |
+
>>> A = Matrix(2, 2, [1, 2, 3, 4])
|
2468 |
+
>>> A.trace()
|
2469 |
+
5
|
2470 |
+
|
2471 |
+
"""
|
2472 |
+
if self.rows != self.cols:
|
2473 |
+
raise NonSquareMatrixError()
|
2474 |
+
return self._eval_trace()
|
2475 |
+
|
2476 |
+
def transpose(self):
|
2477 |
+
"""
|
2478 |
+
Returns the transpose of the matrix.
|
2479 |
+
|
2480 |
+
Examples
|
2481 |
+
========
|
2482 |
+
|
2483 |
+
>>> from sympy import Matrix
|
2484 |
+
>>> A = Matrix(2, 2, [1, 2, 3, 4])
|
2485 |
+
>>> A.transpose()
|
2486 |
+
Matrix([
|
2487 |
+
[1, 3],
|
2488 |
+
[2, 4]])
|
2489 |
+
|
2490 |
+
>>> from sympy import Matrix, I
|
2491 |
+
>>> m=Matrix(((1, 2+I), (3, 4)))
|
2492 |
+
>>> m
|
2493 |
+
Matrix([
|
2494 |
+
[1, 2 + I],
|
2495 |
+
[3, 4]])
|
2496 |
+
>>> m.transpose()
|
2497 |
+
Matrix([
|
2498 |
+
[ 1, 3],
|
2499 |
+
[2 + I, 4]])
|
2500 |
+
>>> m.T == m.transpose()
|
2501 |
+
True
|
2502 |
+
|
2503 |
+
See Also
|
2504 |
+
========
|
2505 |
+
|
2506 |
+
conjugate: By-element conjugation
|
2507 |
+
|
2508 |
+
"""
|
2509 |
+
return self._eval_transpose()
|
2510 |
+
|
2511 |
+
@property
|
2512 |
+
def T(self):
|
2513 |
+
'''Matrix transposition'''
|
2514 |
+
return self.transpose()
|
2515 |
+
|
2516 |
+
@property
|
2517 |
+
def C(self):
|
2518 |
+
'''By-element conjugation'''
|
2519 |
+
return self.conjugate()
|
2520 |
+
|
2521 |
+
def n(self, *args, **kwargs):
|
2522 |
+
"""Apply evalf() to each element of self."""
|
2523 |
+
return self.evalf(*args, **kwargs)
|
2524 |
+
|
2525 |
+
def xreplace(self, rule): # should mirror core.basic.xreplace
|
2526 |
+
"""Return a new matrix with xreplace applied to each entry.
|
2527 |
+
|
2528 |
+
Examples
|
2529 |
+
========
|
2530 |
+
|
2531 |
+
>>> from sympy.abc import x, y
|
2532 |
+
>>> from sympy import SparseMatrix, Matrix
|
2533 |
+
>>> SparseMatrix(1, 1, [x])
|
2534 |
+
Matrix([[x]])
|
2535 |
+
>>> _.xreplace({x: y})
|
2536 |
+
Matrix([[y]])
|
2537 |
+
>>> Matrix(_).xreplace({y: x})
|
2538 |
+
Matrix([[x]])
|
2539 |
+
"""
|
2540 |
+
return self.applyfunc(lambda x: x.xreplace(rule))
|
2541 |
+
|
2542 |
+
def _eval_simplify(self, **kwargs):
|
2543 |
+
# XXX: We can't use self.simplify here as mutable subclasses will
|
2544 |
+
# override simplify and have it return None
|
2545 |
+
return MatrixOperations.simplify(self, **kwargs)
|
2546 |
+
|
2547 |
+
def _eval_trigsimp(self, **opts):
|
2548 |
+
from sympy.simplify.trigsimp import trigsimp
|
2549 |
+
return self.applyfunc(lambda x: trigsimp(x, **opts))
|
2550 |
+
|
2551 |
+
def upper_triangular(self, k=0):
|
2552 |
+
"""Return the elements on and above the kth diagonal of a matrix.
|
2553 |
+
If k is not specified then simply returns upper-triangular portion
|
2554 |
+
of a matrix
|
2555 |
+
|
2556 |
+
Examples
|
2557 |
+
========
|
2558 |
+
|
2559 |
+
>>> from sympy import ones
|
2560 |
+
>>> A = ones(4)
|
2561 |
+
>>> A.upper_triangular()
|
2562 |
+
Matrix([
|
2563 |
+
[1, 1, 1, 1],
|
2564 |
+
[0, 1, 1, 1],
|
2565 |
+
[0, 0, 1, 1],
|
2566 |
+
[0, 0, 0, 1]])
|
2567 |
+
|
2568 |
+
>>> A.upper_triangular(2)
|
2569 |
+
Matrix([
|
2570 |
+
[0, 0, 1, 1],
|
2571 |
+
[0, 0, 0, 1],
|
2572 |
+
[0, 0, 0, 0],
|
2573 |
+
[0, 0, 0, 0]])
|
2574 |
+
|
2575 |
+
>>> A.upper_triangular(-1)
|
2576 |
+
Matrix([
|
2577 |
+
[1, 1, 1, 1],
|
2578 |
+
[1, 1, 1, 1],
|
2579 |
+
[0, 1, 1, 1],
|
2580 |
+
[0, 0, 1, 1]])
|
2581 |
+
|
2582 |
+
"""
|
2583 |
+
|
2584 |
+
def entry(i, j):
|
2585 |
+
return self[i, j] if i + k <= j else self.zero
|
2586 |
+
|
2587 |
+
return self._new(self.rows, self.cols, entry)
|
2588 |
+
|
2589 |
+
|
2590 |
+
def lower_triangular(self, k=0):
|
2591 |
+
"""Return the elements on and below the kth diagonal of a matrix.
|
2592 |
+
If k is not specified then simply returns lower-triangular portion
|
2593 |
+
of a matrix
|
2594 |
+
|
2595 |
+
Examples
|
2596 |
+
========
|
2597 |
+
|
2598 |
+
>>> from sympy import ones
|
2599 |
+
>>> A = ones(4)
|
2600 |
+
>>> A.lower_triangular()
|
2601 |
+
Matrix([
|
2602 |
+
[1, 0, 0, 0],
|
2603 |
+
[1, 1, 0, 0],
|
2604 |
+
[1, 1, 1, 0],
|
2605 |
+
[1, 1, 1, 1]])
|
2606 |
+
|
2607 |
+
>>> A.lower_triangular(-2)
|
2608 |
+
Matrix([
|
2609 |
+
[0, 0, 0, 0],
|
2610 |
+
[0, 0, 0, 0],
|
2611 |
+
[1, 0, 0, 0],
|
2612 |
+
[1, 1, 0, 0]])
|
2613 |
+
|
2614 |
+
>>> A.lower_triangular(1)
|
2615 |
+
Matrix([
|
2616 |
+
[1, 1, 0, 0],
|
2617 |
+
[1, 1, 1, 0],
|
2618 |
+
[1, 1, 1, 1],
|
2619 |
+
[1, 1, 1, 1]])
|
2620 |
+
|
2621 |
+
"""
|
2622 |
+
|
2623 |
+
def entry(i, j):
|
2624 |
+
return self[i, j] if i + k >= j else self.zero
|
2625 |
+
|
2626 |
+
return self._new(self.rows, self.cols, entry)
|
2627 |
+
|
2628 |
+
|
2629 |
+
|
2630 |
+
class MatrixArithmetic(MatrixRequired):
|
2631 |
+
"""Provides basic matrix arithmetic operations.
|
2632 |
+
Should not be instantiated directly."""
|
2633 |
+
|
2634 |
+
_op_priority = 10.01
|
2635 |
+
|
2636 |
+
def _eval_Abs(self):
|
2637 |
+
return self._new(self.rows, self.cols, lambda i, j: Abs(self[i, j]))
|
2638 |
+
|
2639 |
+
def _eval_add(self, other):
|
2640 |
+
return self._new(self.rows, self.cols,
|
2641 |
+
lambda i, j: self[i, j] + other[i, j])
|
2642 |
+
|
2643 |
+
def _eval_matrix_mul(self, other):
|
2644 |
+
def entry(i, j):
|
2645 |
+
vec = [self[i,k]*other[k,j] for k in range(self.cols)]
|
2646 |
+
try:
|
2647 |
+
return Add(*vec)
|
2648 |
+
except (TypeError, SympifyError):
|
2649 |
+
# Some matrices don't work with `sum` or `Add`
|
2650 |
+
# They don't work with `sum` because `sum` tries to add `0`
|
2651 |
+
# Fall back to a safe way to multiply if the `Add` fails.
|
2652 |
+
return reduce(lambda a, b: a + b, vec)
|
2653 |
+
|
2654 |
+
return self._new(self.rows, other.cols, entry)
|
2655 |
+
|
2656 |
+
def _eval_matrix_mul_elementwise(self, other):
|
2657 |
+
return self._new(self.rows, self.cols, lambda i, j: self[i,j]*other[i,j])
|
2658 |
+
|
2659 |
+
def _eval_matrix_rmul(self, other):
|
2660 |
+
def entry(i, j):
|
2661 |
+
return sum(other[i,k]*self[k,j] for k in range(other.cols))
|
2662 |
+
return self._new(other.rows, self.cols, entry)
|
2663 |
+
|
2664 |
+
def _eval_pow_by_recursion(self, num):
|
2665 |
+
if num == 1:
|
2666 |
+
return self
|
2667 |
+
|
2668 |
+
if num % 2 == 1:
|
2669 |
+
a, b = self, self._eval_pow_by_recursion(num - 1)
|
2670 |
+
else:
|
2671 |
+
a = b = self._eval_pow_by_recursion(num // 2)
|
2672 |
+
|
2673 |
+
return a.multiply(b)
|
2674 |
+
|
2675 |
+
def _eval_pow_by_cayley(self, exp):
|
2676 |
+
from sympy.discrete.recurrences import linrec_coeffs
|
2677 |
+
row = self.shape[0]
|
2678 |
+
p = self.charpoly()
|
2679 |
+
|
2680 |
+
coeffs = (-p).all_coeffs()[1:]
|
2681 |
+
coeffs = linrec_coeffs(coeffs, exp)
|
2682 |
+
new_mat = self.eye(row)
|
2683 |
+
ans = self.zeros(row)
|
2684 |
+
|
2685 |
+
for i in range(row):
|
2686 |
+
ans += coeffs[i]*new_mat
|
2687 |
+
new_mat *= self
|
2688 |
+
|
2689 |
+
return ans
|
2690 |
+
|
2691 |
+
def _eval_pow_by_recursion_dotprodsimp(self, num, prevsimp=None):
|
2692 |
+
if prevsimp is None:
|
2693 |
+
prevsimp = [True]*len(self)
|
2694 |
+
|
2695 |
+
if num == 1:
|
2696 |
+
return self
|
2697 |
+
|
2698 |
+
if num % 2 == 1:
|
2699 |
+
a, b = self, self._eval_pow_by_recursion_dotprodsimp(num - 1,
|
2700 |
+
prevsimp=prevsimp)
|
2701 |
+
else:
|
2702 |
+
a = b = self._eval_pow_by_recursion_dotprodsimp(num // 2,
|
2703 |
+
prevsimp=prevsimp)
|
2704 |
+
|
2705 |
+
m = a.multiply(b, dotprodsimp=False)
|
2706 |
+
lenm = len(m)
|
2707 |
+
elems = [None]*lenm
|
2708 |
+
|
2709 |
+
for i in range(lenm):
|
2710 |
+
if prevsimp[i]:
|
2711 |
+
elems[i], prevsimp[i] = _dotprodsimp(m[i], withsimp=True)
|
2712 |
+
else:
|
2713 |
+
elems[i] = m[i]
|
2714 |
+
|
2715 |
+
return m._new(m.rows, m.cols, elems)
|
2716 |
+
|
2717 |
+
def _eval_scalar_mul(self, other):
|
2718 |
+
return self._new(self.rows, self.cols, lambda i, j: self[i,j]*other)
|
2719 |
+
|
2720 |
+
def _eval_scalar_rmul(self, other):
|
2721 |
+
return self._new(self.rows, self.cols, lambda i, j: other*self[i,j])
|
2722 |
+
|
2723 |
+
def _eval_Mod(self, other):
|
2724 |
+
return self._new(self.rows, self.cols, lambda i, j: Mod(self[i, j], other))
|
2725 |
+
|
2726 |
+
# Python arithmetic functions
|
2727 |
+
def __abs__(self):
|
2728 |
+
"""Returns a new matrix with entry-wise absolute values."""
|
2729 |
+
return self._eval_Abs()
|
2730 |
+
|
2731 |
+
@call_highest_priority('__radd__')
|
2732 |
+
def __add__(self, other):
|
2733 |
+
"""Return self + other, raising ShapeError if shapes do not match."""
|
2734 |
+
if isinstance(other, NDimArray): # Matrix and array addition is currently not implemented
|
2735 |
+
return NotImplemented
|
2736 |
+
other = _matrixify(other)
|
2737 |
+
# matrix-like objects can have shapes. This is
|
2738 |
+
# our first sanity check.
|
2739 |
+
if hasattr(other, 'shape'):
|
2740 |
+
if self.shape != other.shape:
|
2741 |
+
raise ShapeError("Matrix size mismatch: %s + %s" % (
|
2742 |
+
self.shape, other.shape))
|
2743 |
+
|
2744 |
+
# honest SymPy matrices defer to their class's routine
|
2745 |
+
if getattr(other, 'is_Matrix', False):
|
2746 |
+
# call the highest-priority class's _eval_add
|
2747 |
+
a, b = self, other
|
2748 |
+
if a.__class__ != classof(a, b):
|
2749 |
+
b, a = a, b
|
2750 |
+
return a._eval_add(b)
|
2751 |
+
# Matrix-like objects can be passed to CommonMatrix routines directly.
|
2752 |
+
if getattr(other, 'is_MatrixLike', False):
|
2753 |
+
return MatrixArithmetic._eval_add(self, other)
|
2754 |
+
|
2755 |
+
raise TypeError('cannot add %s and %s' % (type(self), type(other)))
|
2756 |
+
|
2757 |
+
@call_highest_priority('__rtruediv__')
|
2758 |
+
def __truediv__(self, other):
|
2759 |
+
return self * (self.one / other)
|
2760 |
+
|
2761 |
+
@call_highest_priority('__rmatmul__')
|
2762 |
+
def __matmul__(self, other):
|
2763 |
+
other = _matrixify(other)
|
2764 |
+
if not getattr(other, 'is_Matrix', False) and not getattr(other, 'is_MatrixLike', False):
|
2765 |
+
return NotImplemented
|
2766 |
+
|
2767 |
+
return self.__mul__(other)
|
2768 |
+
|
2769 |
+
def __mod__(self, other):
|
2770 |
+
return self.applyfunc(lambda x: x % other)
|
2771 |
+
|
2772 |
+
@call_highest_priority('__rmul__')
|
2773 |
+
def __mul__(self, other):
|
2774 |
+
"""Return self*other where other is either a scalar or a matrix
|
2775 |
+
of compatible dimensions.
|
2776 |
+
|
2777 |
+
Examples
|
2778 |
+
========
|
2779 |
+
|
2780 |
+
>>> from sympy import Matrix
|
2781 |
+
>>> A = Matrix([[1, 2, 3], [4, 5, 6]])
|
2782 |
+
>>> 2*A == A*2 == Matrix([[2, 4, 6], [8, 10, 12]])
|
2783 |
+
True
|
2784 |
+
>>> B = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
|
2785 |
+
>>> A*B
|
2786 |
+
Matrix([
|
2787 |
+
[30, 36, 42],
|
2788 |
+
[66, 81, 96]])
|
2789 |
+
>>> B*A
|
2790 |
+
Traceback (most recent call last):
|
2791 |
+
...
|
2792 |
+
ShapeError: Matrices size mismatch.
|
2793 |
+
>>>
|
2794 |
+
|
2795 |
+
See Also
|
2796 |
+
========
|
2797 |
+
|
2798 |
+
matrix_multiply_elementwise
|
2799 |
+
"""
|
2800 |
+
|
2801 |
+
return self.multiply(other)
|
2802 |
+
|
2803 |
+
def multiply(self, other, dotprodsimp=None):
|
2804 |
+
"""Same as __mul__() but with optional simplification.
|
2805 |
+
|
2806 |
+
Parameters
|
2807 |
+
==========
|
2808 |
+
|
2809 |
+
dotprodsimp : bool, optional
|
2810 |
+
Specifies whether intermediate term algebraic simplification is used
|
2811 |
+
during matrix multiplications to control expression blowup and thus
|
2812 |
+
speed up calculation. Default is off.
|
2813 |
+
"""
|
2814 |
+
|
2815 |
+
isimpbool = _get_intermediate_simp_bool(False, dotprodsimp)
|
2816 |
+
other = _matrixify(other)
|
2817 |
+
# matrix-like objects can have shapes. This is
|
2818 |
+
# our first sanity check. Double check other is not explicitly not a Matrix.
|
2819 |
+
if (hasattr(other, 'shape') and len(other.shape) == 2 and
|
2820 |
+
(getattr(other, 'is_Matrix', True) or
|
2821 |
+
getattr(other, 'is_MatrixLike', True))):
|
2822 |
+
if self.shape[1] != other.shape[0]:
|
2823 |
+
raise ShapeError("Matrix size mismatch: %s * %s." % (
|
2824 |
+
self.shape, other.shape))
|
2825 |
+
|
2826 |
+
# honest SymPy matrices defer to their class's routine
|
2827 |
+
if getattr(other, 'is_Matrix', False):
|
2828 |
+
m = self._eval_matrix_mul(other)
|
2829 |
+
if isimpbool:
|
2830 |
+
return m._new(m.rows, m.cols, [_dotprodsimp(e) for e in m])
|
2831 |
+
return m
|
2832 |
+
|
2833 |
+
# Matrix-like objects can be passed to CommonMatrix routines directly.
|
2834 |
+
if getattr(other, 'is_MatrixLike', False):
|
2835 |
+
return MatrixArithmetic._eval_matrix_mul(self, other)
|
2836 |
+
|
2837 |
+
# if 'other' is not iterable then scalar multiplication.
|
2838 |
+
if not isinstance(other, Iterable):
|
2839 |
+
try:
|
2840 |
+
return self._eval_scalar_mul(other)
|
2841 |
+
except TypeError:
|
2842 |
+
pass
|
2843 |
+
|
2844 |
+
return NotImplemented
|
2845 |
+
|
2846 |
+
def multiply_elementwise(self, other):
|
2847 |
+
"""Return the Hadamard product (elementwise product) of A and B
|
2848 |
+
|
2849 |
+
Examples
|
2850 |
+
========
|
2851 |
+
|
2852 |
+
>>> from sympy import Matrix
|
2853 |
+
>>> A = Matrix([[0, 1, 2], [3, 4, 5]])
|
2854 |
+
>>> B = Matrix([[1, 10, 100], [100, 10, 1]])
|
2855 |
+
>>> A.multiply_elementwise(B)
|
2856 |
+
Matrix([
|
2857 |
+
[ 0, 10, 200],
|
2858 |
+
[300, 40, 5]])
|
2859 |
+
|
2860 |
+
See Also
|
2861 |
+
========
|
2862 |
+
|
2863 |
+
sympy.matrices.matrixbase.MatrixBase.cross
|
2864 |
+
sympy.matrices.matrixbase.MatrixBase.dot
|
2865 |
+
multiply
|
2866 |
+
"""
|
2867 |
+
if self.shape != other.shape:
|
2868 |
+
raise ShapeError("Matrix shapes must agree {} != {}".format(self.shape, other.shape))
|
2869 |
+
|
2870 |
+
return self._eval_matrix_mul_elementwise(other)
|
2871 |
+
|
2872 |
+
def __neg__(self):
|
2873 |
+
return self._eval_scalar_mul(-1)
|
2874 |
+
|
2875 |
+
@call_highest_priority('__rpow__')
|
2876 |
+
def __pow__(self, exp):
|
2877 |
+
"""Return self**exp a scalar or symbol."""
|
2878 |
+
|
2879 |
+
return self.pow(exp)
|
2880 |
+
|
2881 |
+
|
2882 |
+
def pow(self, exp, method=None):
|
2883 |
+
r"""Return self**exp a scalar or symbol.
|
2884 |
+
|
2885 |
+
Parameters
|
2886 |
+
==========
|
2887 |
+
|
2888 |
+
method : multiply, mulsimp, jordan, cayley
|
2889 |
+
If multiply then it returns exponentiation using recursion.
|
2890 |
+
If jordan then Jordan form exponentiation will be used.
|
2891 |
+
If cayley then the exponentiation is done using Cayley-Hamilton
|
2892 |
+
theorem.
|
2893 |
+
If mulsimp then the exponentiation is done using recursion
|
2894 |
+
with dotprodsimp. This specifies whether intermediate term
|
2895 |
+
algebraic simplification is used during naive matrix power to
|
2896 |
+
control expression blowup and thus speed up calculation.
|
2897 |
+
If None, then it heuristically decides which method to use.
|
2898 |
+
|
2899 |
+
"""
|
2900 |
+
|
2901 |
+
if method is not None and method not in ['multiply', 'mulsimp', 'jordan', 'cayley']:
|
2902 |
+
raise TypeError('No such method')
|
2903 |
+
if self.rows != self.cols:
|
2904 |
+
raise NonSquareMatrixError()
|
2905 |
+
a = self
|
2906 |
+
jordan_pow = getattr(a, '_matrix_pow_by_jordan_blocks', None)
|
2907 |
+
exp = sympify(exp)
|
2908 |
+
|
2909 |
+
if exp.is_zero:
|
2910 |
+
return a._new(a.rows, a.cols, lambda i, j: int(i == j))
|
2911 |
+
if exp == 1:
|
2912 |
+
return a
|
2913 |
+
|
2914 |
+
diagonal = getattr(a, 'is_diagonal', None)
|
2915 |
+
if diagonal is not None and diagonal():
|
2916 |
+
return a._new(a.rows, a.cols, lambda i, j: a[i,j]**exp if i == j else 0)
|
2917 |
+
|
2918 |
+
if exp.is_Number and exp % 1 == 0:
|
2919 |
+
if a.rows == 1:
|
2920 |
+
return a._new([[a[0]**exp]])
|
2921 |
+
if exp < 0:
|
2922 |
+
exp = -exp
|
2923 |
+
a = a.inv()
|
2924 |
+
# When certain conditions are met,
|
2925 |
+
# Jordan block algorithm is faster than
|
2926 |
+
# computation by recursion.
|
2927 |
+
if method == 'jordan':
|
2928 |
+
try:
|
2929 |
+
return jordan_pow(exp)
|
2930 |
+
except MatrixError:
|
2931 |
+
if method == 'jordan':
|
2932 |
+
raise
|
2933 |
+
|
2934 |
+
elif method == 'cayley':
|
2935 |
+
if not exp.is_Number or exp % 1 != 0:
|
2936 |
+
raise ValueError("cayley method is only valid for integer powers")
|
2937 |
+
return a._eval_pow_by_cayley(exp)
|
2938 |
+
|
2939 |
+
elif method == "mulsimp":
|
2940 |
+
if not exp.is_Number or exp % 1 != 0:
|
2941 |
+
raise ValueError("mulsimp method is only valid for integer powers")
|
2942 |
+
return a._eval_pow_by_recursion_dotprodsimp(exp)
|
2943 |
+
|
2944 |
+
elif method == "multiply":
|
2945 |
+
if not exp.is_Number or exp % 1 != 0:
|
2946 |
+
raise ValueError("multiply method is only valid for integer powers")
|
2947 |
+
return a._eval_pow_by_recursion(exp)
|
2948 |
+
|
2949 |
+
elif method is None and exp.is_Number and exp % 1 == 0:
|
2950 |
+
if exp.is_Float:
|
2951 |
+
exp = Integer(exp)
|
2952 |
+
# Decide heuristically which method to apply
|
2953 |
+
if a.rows == 2 and exp > 100000:
|
2954 |
+
return jordan_pow(exp)
|
2955 |
+
elif _get_intermediate_simp_bool(True, None):
|
2956 |
+
return a._eval_pow_by_recursion_dotprodsimp(exp)
|
2957 |
+
elif exp > 10000:
|
2958 |
+
return a._eval_pow_by_cayley(exp)
|
2959 |
+
else:
|
2960 |
+
return a._eval_pow_by_recursion(exp)
|
2961 |
+
|
2962 |
+
if jordan_pow:
|
2963 |
+
try:
|
2964 |
+
return jordan_pow(exp)
|
2965 |
+
except NonInvertibleMatrixError:
|
2966 |
+
# Raised by jordan_pow on zero determinant matrix unless exp is
|
2967 |
+
# definitely known to be a non-negative integer.
|
2968 |
+
# Here we raise if n is definitely not a non-negative integer
|
2969 |
+
# but otherwise we can leave this as an unevaluated MatPow.
|
2970 |
+
if exp.is_integer is False or exp.is_nonnegative is False:
|
2971 |
+
raise
|
2972 |
+
|
2973 |
+
from sympy.matrices.expressions import MatPow
|
2974 |
+
return MatPow(a, exp)
|
2975 |
+
|
2976 |
+
@call_highest_priority('__add__')
|
2977 |
+
def __radd__(self, other):
|
2978 |
+
return self + other
|
2979 |
+
|
2980 |
+
@call_highest_priority('__matmul__')
|
2981 |
+
def __rmatmul__(self, other):
|
2982 |
+
other = _matrixify(other)
|
2983 |
+
if not getattr(other, 'is_Matrix', False) and not getattr(other, 'is_MatrixLike', False):
|
2984 |
+
return NotImplemented
|
2985 |
+
|
2986 |
+
return self.__rmul__(other)
|
2987 |
+
|
2988 |
+
@call_highest_priority('__mul__')
|
2989 |
+
def __rmul__(self, other):
|
2990 |
+
return self.rmultiply(other)
|
2991 |
+
|
2992 |
+
def rmultiply(self, other, dotprodsimp=None):
|
2993 |
+
"""Same as __rmul__() but with optional simplification.
|
2994 |
+
|
2995 |
+
Parameters
|
2996 |
+
==========
|
2997 |
+
|
2998 |
+
dotprodsimp : bool, optional
|
2999 |
+
Specifies whether intermediate term algebraic simplification is used
|
3000 |
+
during matrix multiplications to control expression blowup and thus
|
3001 |
+
speed up calculation. Default is off.
|
3002 |
+
"""
|
3003 |
+
isimpbool = _get_intermediate_simp_bool(False, dotprodsimp)
|
3004 |
+
other = _matrixify(other)
|
3005 |
+
# matrix-like objects can have shapes. This is
|
3006 |
+
# our first sanity check. Double check other is not explicitly not a Matrix.
|
3007 |
+
if (hasattr(other, 'shape') and len(other.shape) == 2 and
|
3008 |
+
(getattr(other, 'is_Matrix', True) or
|
3009 |
+
getattr(other, 'is_MatrixLike', True))):
|
3010 |
+
if self.shape[0] != other.shape[1]:
|
3011 |
+
raise ShapeError("Matrix size mismatch.")
|
3012 |
+
|
3013 |
+
# honest SymPy matrices defer to their class's routine
|
3014 |
+
if getattr(other, 'is_Matrix', False):
|
3015 |
+
m = self._eval_matrix_rmul(other)
|
3016 |
+
if isimpbool:
|
3017 |
+
return m._new(m.rows, m.cols, [_dotprodsimp(e) for e in m])
|
3018 |
+
return m
|
3019 |
+
# Matrix-like objects can be passed to CommonMatrix routines directly.
|
3020 |
+
if getattr(other, 'is_MatrixLike', False):
|
3021 |
+
return MatrixArithmetic._eval_matrix_rmul(self, other)
|
3022 |
+
|
3023 |
+
# if 'other' is not iterable then scalar multiplication.
|
3024 |
+
if not isinstance(other, Iterable):
|
3025 |
+
try:
|
3026 |
+
return self._eval_scalar_rmul(other)
|
3027 |
+
except TypeError:
|
3028 |
+
pass
|
3029 |
+
|
3030 |
+
return NotImplemented
|
3031 |
+
|
3032 |
+
@call_highest_priority('__sub__')
|
3033 |
+
def __rsub__(self, a):
|
3034 |
+
return (-self) + a
|
3035 |
+
|
3036 |
+
@call_highest_priority('__rsub__')
|
3037 |
+
def __sub__(self, a):
|
3038 |
+
return self + (-a)
|
3039 |
+
|
3040 |
+
|
3041 |
+
class MatrixCommon(MatrixArithmetic, MatrixOperations, MatrixProperties,
|
3042 |
+
MatrixSpecial, MatrixShaping):
|
3043 |
+
"""All common matrix operations including basic arithmetic, shaping,
|
3044 |
+
and special matrices like `zeros`, and `eye`."""
|
3045 |
+
_diff_wrt = True # type: bool
|
3046 |
+
|
3047 |
+
|
3048 |
+
class _MinimalMatrix:
|
3049 |
+
"""Class providing the minimum functionality
|
3050 |
+
for a matrix-like object and implementing every method
|
3051 |
+
required for a `MatrixRequired`. This class does not have everything
|
3052 |
+
needed to become a full-fledged SymPy object, but it will satisfy the
|
3053 |
+
requirements of anything inheriting from `MatrixRequired`. If you wish
|
3054 |
+
to make a specialized matrix type, make sure to implement these
|
3055 |
+
methods and properties with the exception of `__init__` and `__repr__`
|
3056 |
+
which are included for convenience."""
|
3057 |
+
|
3058 |
+
is_MatrixLike = True
|
3059 |
+
_sympify = staticmethod(sympify)
|
3060 |
+
_class_priority = 3
|
3061 |
+
zero = S.Zero
|
3062 |
+
one = S.One
|
3063 |
+
|
3064 |
+
is_Matrix = True
|
3065 |
+
is_MatrixExpr = False
|
3066 |
+
|
3067 |
+
@classmethod
|
3068 |
+
def _new(cls, *args, **kwargs):
|
3069 |
+
return cls(*args, **kwargs)
|
3070 |
+
|
3071 |
+
def __init__(self, rows, cols=None, mat=None, copy=False):
|
3072 |
+
if isfunction(mat):
|
3073 |
+
# if we passed in a function, use that to populate the indices
|
3074 |
+
mat = [mat(i, j) for i in range(rows) for j in range(cols)]
|
3075 |
+
if cols is None and mat is None:
|
3076 |
+
mat = rows
|
3077 |
+
rows, cols = getattr(mat, 'shape', (rows, cols))
|
3078 |
+
try:
|
3079 |
+
# if we passed in a list of lists, flatten it and set the size
|
3080 |
+
if cols is None and mat is None:
|
3081 |
+
mat = rows
|
3082 |
+
cols = len(mat[0])
|
3083 |
+
rows = len(mat)
|
3084 |
+
mat = [x for l in mat for x in l]
|
3085 |
+
except (IndexError, TypeError):
|
3086 |
+
pass
|
3087 |
+
self.mat = tuple(self._sympify(x) for x in mat)
|
3088 |
+
self.rows, self.cols = rows, cols
|
3089 |
+
if self.rows is None or self.cols is None:
|
3090 |
+
raise NotImplementedError("Cannot initialize matrix with given parameters")
|
3091 |
+
|
3092 |
+
def __getitem__(self, key):
|
3093 |
+
def _normalize_slices(row_slice, col_slice):
|
3094 |
+
"""Ensure that row_slice and col_slice do not have
|
3095 |
+
`None` in their arguments. Any integers are converted
|
3096 |
+
to slices of length 1"""
|
3097 |
+
if not isinstance(row_slice, slice):
|
3098 |
+
row_slice = slice(row_slice, row_slice + 1, None)
|
3099 |
+
row_slice = slice(*row_slice.indices(self.rows))
|
3100 |
+
|
3101 |
+
if not isinstance(col_slice, slice):
|
3102 |
+
col_slice = slice(col_slice, col_slice + 1, None)
|
3103 |
+
col_slice = slice(*col_slice.indices(self.cols))
|
3104 |
+
|
3105 |
+
return (row_slice, col_slice)
|
3106 |
+
|
3107 |
+
def _coord_to_index(i, j):
|
3108 |
+
"""Return the index in _mat corresponding
|
3109 |
+
to the (i,j) position in the matrix. """
|
3110 |
+
return i * self.cols + j
|
3111 |
+
|
3112 |
+
if isinstance(key, tuple):
|
3113 |
+
i, j = key
|
3114 |
+
if isinstance(i, slice) or isinstance(j, slice):
|
3115 |
+
# if the coordinates are not slices, make them so
|
3116 |
+
# and expand the slices so they don't contain `None`
|
3117 |
+
i, j = _normalize_slices(i, j)
|
3118 |
+
|
3119 |
+
rowsList, colsList = list(range(self.rows))[i], \
|
3120 |
+
list(range(self.cols))[j]
|
3121 |
+
indices = (i * self.cols + j for i in rowsList for j in
|
3122 |
+
colsList)
|
3123 |
+
return self._new(len(rowsList), len(colsList),
|
3124 |
+
[self.mat[i] for i in indices])
|
3125 |
+
|
3126 |
+
# if the key is a tuple of ints, change
|
3127 |
+
# it to an array index
|
3128 |
+
key = _coord_to_index(i, j)
|
3129 |
+
return self.mat[key]
|
3130 |
+
|
3131 |
+
def __eq__(self, other):
|
3132 |
+
try:
|
3133 |
+
classof(self, other)
|
3134 |
+
except TypeError:
|
3135 |
+
return False
|
3136 |
+
return (
|
3137 |
+
self.shape == other.shape and list(self) == list(other))
|
3138 |
+
|
3139 |
+
def __len__(self):
|
3140 |
+
return self.rows*self.cols
|
3141 |
+
|
3142 |
+
def __repr__(self):
|
3143 |
+
return "_MinimalMatrix({}, {}, {})".format(self.rows, self.cols,
|
3144 |
+
self.mat)
|
3145 |
+
|
3146 |
+
@property
|
3147 |
+
def shape(self):
|
3148 |
+
return (self.rows, self.cols)
|
3149 |
+
|
3150 |
+
|
3151 |
+
class _CastableMatrix: # this is needed here ONLY FOR TESTS.
|
3152 |
+
def as_mutable(self):
|
3153 |
+
return self
|
3154 |
+
|
3155 |
+
def as_immutable(self):
|
3156 |
+
return self
|
3157 |
+
|
3158 |
+
|
3159 |
+
class _MatrixWrapper:
|
3160 |
+
"""Wrapper class providing the minimum functionality for a matrix-like
|
3161 |
+
object: .rows, .cols, .shape, indexability, and iterability. CommonMatrix
|
3162 |
+
math operations should work on matrix-like objects. This one is intended for
|
3163 |
+
matrix-like objects which use the same indexing format as SymPy with respect
|
3164 |
+
to returning matrix elements instead of rows for non-tuple indexes.
|
3165 |
+
"""
|
3166 |
+
|
3167 |
+
is_Matrix = False # needs to be here because of __getattr__
|
3168 |
+
is_MatrixLike = True
|
3169 |
+
|
3170 |
+
def __init__(self, mat, shape):
|
3171 |
+
self.mat = mat
|
3172 |
+
self.shape = shape
|
3173 |
+
self.rows, self.cols = shape
|
3174 |
+
|
3175 |
+
def __getitem__(self, key):
|
3176 |
+
if isinstance(key, tuple):
|
3177 |
+
return sympify(self.mat.__getitem__(key))
|
3178 |
+
|
3179 |
+
return sympify(self.mat.__getitem__((key // self.rows, key % self.cols)))
|
3180 |
+
|
3181 |
+
def __iter__(self): # supports numpy.matrix and numpy.array
|
3182 |
+
mat = self.mat
|
3183 |
+
cols = self.cols
|
3184 |
+
|
3185 |
+
return iter(sympify(mat[r, c]) for r in range(self.rows) for c in range(cols))
|
3186 |
+
|
3187 |
+
|
3188 |
+
def _matrixify(mat):
|
3189 |
+
"""If `mat` is a Matrix or is matrix-like,
|
3190 |
+
return a Matrix or MatrixWrapper object. Otherwise
|
3191 |
+
`mat` is passed through without modification."""
|
3192 |
+
|
3193 |
+
if getattr(mat, 'is_Matrix', False) or getattr(mat, 'is_MatrixLike', False):
|
3194 |
+
return mat
|
3195 |
+
|
3196 |
+
if not(getattr(mat, 'is_Matrix', True) or getattr(mat, 'is_MatrixLike', True)):
|
3197 |
+
return mat
|
3198 |
+
|
3199 |
+
shape = None
|
3200 |
+
|
3201 |
+
if hasattr(mat, 'shape'): # numpy, scipy.sparse
|
3202 |
+
if len(mat.shape) == 2:
|
3203 |
+
shape = mat.shape
|
3204 |
+
elif hasattr(mat, 'rows') and hasattr(mat, 'cols'): # mpmath
|
3205 |
+
shape = (mat.rows, mat.cols)
|
3206 |
+
|
3207 |
+
if shape:
|
3208 |
+
return _MatrixWrapper(mat, shape)
|
3209 |
+
|
3210 |
+
return mat
|
3211 |
+
|
3212 |
+
|
3213 |
+
def a2idx(j, n=None):
|
3214 |
+
"""Return integer after making positive and validating against n."""
|
3215 |
+
if not isinstance(j, int):
|
3216 |
+
jindex = getattr(j, '__index__', None)
|
3217 |
+
if jindex is not None:
|
3218 |
+
j = jindex()
|
3219 |
+
else:
|
3220 |
+
raise IndexError("Invalid index a[%r]" % (j,))
|
3221 |
+
if n is not None:
|
3222 |
+
if j < 0:
|
3223 |
+
j += n
|
3224 |
+
if not (j >= 0 and j < n):
|
3225 |
+
raise IndexError("Index out of range: a[%s]" % (j,))
|
3226 |
+
return int(j)
|
3227 |
+
|
3228 |
+
|
3229 |
+
def classof(A, B):
|
3230 |
+
"""
|
3231 |
+
Get the type of the result when combining matrices of different types.
|
3232 |
+
|
3233 |
+
Currently the strategy is that immutability is contagious.
|
3234 |
+
|
3235 |
+
Examples
|
3236 |
+
========
|
3237 |
+
|
3238 |
+
>>> from sympy import Matrix, ImmutableMatrix
|
3239 |
+
>>> from sympy.matrices.matrixbase import classof
|
3240 |
+
>>> M = Matrix([[1, 2], [3, 4]]) # a Mutable Matrix
|
3241 |
+
>>> IM = ImmutableMatrix([[1, 2], [3, 4]])
|
3242 |
+
>>> classof(M, IM)
|
3243 |
+
<class 'sympy.matrices.immutable.ImmutableDenseMatrix'>
|
3244 |
+
"""
|
3245 |
+
priority_A = getattr(A, '_class_priority', None)
|
3246 |
+
priority_B = getattr(B, '_class_priority', None)
|
3247 |
+
if None not in (priority_A, priority_B):
|
3248 |
+
if A._class_priority > B._class_priority:
|
3249 |
+
return A.__class__
|
3250 |
+
else:
|
3251 |
+
return B.__class__
|
3252 |
+
|
3253 |
+
try:
|
3254 |
+
import numpy
|
3255 |
+
except ImportError:
|
3256 |
+
pass
|
3257 |
+
else:
|
3258 |
+
if isinstance(A, numpy.ndarray):
|
3259 |
+
return B.__class__
|
3260 |
+
if isinstance(B, numpy.ndarray):
|
3261 |
+
return A.__class__
|
3262 |
+
|
3263 |
+
raise TypeError("Incompatible classes %s, %s" % (A.__class__, B.__class__))
|
.venv/lib/python3.11/site-packages/sympy/matrices/determinant.py
ADDED
@@ -0,0 +1,1021 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from types import FunctionType
|
2 |
+
|
3 |
+
from sympy.core.cache import cacheit
|
4 |
+
from sympy.core.numbers import Float, Integer
|
5 |
+
from sympy.core.singleton import S
|
6 |
+
from sympy.core.symbol import uniquely_named_symbol
|
7 |
+
from sympy.core.mul import Mul
|
8 |
+
from sympy.polys import PurePoly, cancel
|
9 |
+
from sympy.functions.combinatorial.numbers import nC
|
10 |
+
from sympy.polys.matrices.domainmatrix import DomainMatrix
|
11 |
+
from sympy.polys.matrices.ddm import DDM
|
12 |
+
|
13 |
+
from .exceptions import NonSquareMatrixError
|
14 |
+
from .utilities import (
|
15 |
+
_get_intermediate_simp, _get_intermediate_simp_bool,
|
16 |
+
_iszero, _is_zero_after_expand_mul, _dotprodsimp, _simplify)
|
17 |
+
|
18 |
+
|
19 |
+
def _find_reasonable_pivot(col, iszerofunc=_iszero, simpfunc=_simplify):
|
20 |
+
""" Find the lowest index of an item in ``col`` that is
|
21 |
+
suitable for a pivot. If ``col`` consists only of
|
22 |
+
Floats, the pivot with the largest norm is returned.
|
23 |
+
Otherwise, the first element where ``iszerofunc`` returns
|
24 |
+
False is used. If ``iszerofunc`` does not return false,
|
25 |
+
items are simplified and retested until a suitable
|
26 |
+
pivot is found.
|
27 |
+
|
28 |
+
Returns a 4-tuple
|
29 |
+
(pivot_offset, pivot_val, assumed_nonzero, newly_determined)
|
30 |
+
where pivot_offset is the index of the pivot, pivot_val is
|
31 |
+
the (possibly simplified) value of the pivot, assumed_nonzero
|
32 |
+
is True if an assumption that the pivot was non-zero
|
33 |
+
was made without being proved, and newly_determined are
|
34 |
+
elements that were simplified during the process of pivot
|
35 |
+
finding."""
|
36 |
+
|
37 |
+
newly_determined = []
|
38 |
+
col = list(col)
|
39 |
+
# a column that contains a mix of floats and integers
|
40 |
+
# but at least one float is considered a numerical
|
41 |
+
# column, and so we do partial pivoting
|
42 |
+
if all(isinstance(x, (Float, Integer)) for x in col) and any(
|
43 |
+
isinstance(x, Float) for x in col):
|
44 |
+
col_abs = [abs(x) for x in col]
|
45 |
+
max_value = max(col_abs)
|
46 |
+
if iszerofunc(max_value):
|
47 |
+
# just because iszerofunc returned True, doesn't
|
48 |
+
# mean the value is numerically zero. Make sure
|
49 |
+
# to replace all entries with numerical zeros
|
50 |
+
if max_value != 0:
|
51 |
+
newly_determined = [(i, 0) for i, x in enumerate(col) if x != 0]
|
52 |
+
return (None, None, False, newly_determined)
|
53 |
+
index = col_abs.index(max_value)
|
54 |
+
return (index, col[index], False, newly_determined)
|
55 |
+
|
56 |
+
# PASS 1 (iszerofunc directly)
|
57 |
+
possible_zeros = []
|
58 |
+
for i, x in enumerate(col):
|
59 |
+
is_zero = iszerofunc(x)
|
60 |
+
# is someone wrote a custom iszerofunc, it may return
|
61 |
+
# BooleanFalse or BooleanTrue instead of True or False,
|
62 |
+
# so use == for comparison instead of `is`
|
63 |
+
if is_zero == False:
|
64 |
+
# we found something that is definitely not zero
|
65 |
+
return (i, x, False, newly_determined)
|
66 |
+
possible_zeros.append(is_zero)
|
67 |
+
|
68 |
+
# by this point, we've found no certain non-zeros
|
69 |
+
if all(possible_zeros):
|
70 |
+
# if everything is definitely zero, we have
|
71 |
+
# no pivot
|
72 |
+
return (None, None, False, newly_determined)
|
73 |
+
|
74 |
+
# PASS 2 (iszerofunc after simplify)
|
75 |
+
# we haven't found any for-sure non-zeros, so
|
76 |
+
# go through the elements iszerofunc couldn't
|
77 |
+
# make a determination about and opportunistically
|
78 |
+
# simplify to see if we find something
|
79 |
+
for i, x in enumerate(col):
|
80 |
+
if possible_zeros[i] is not None:
|
81 |
+
continue
|
82 |
+
simped = simpfunc(x)
|
83 |
+
is_zero = iszerofunc(simped)
|
84 |
+
if is_zero in (True, False):
|
85 |
+
newly_determined.append((i, simped))
|
86 |
+
if is_zero == False:
|
87 |
+
return (i, simped, False, newly_determined)
|
88 |
+
possible_zeros[i] = is_zero
|
89 |
+
|
90 |
+
# after simplifying, some things that were recognized
|
91 |
+
# as zeros might be zeros
|
92 |
+
if all(possible_zeros):
|
93 |
+
# if everything is definitely zero, we have
|
94 |
+
# no pivot
|
95 |
+
return (None, None, False, newly_determined)
|
96 |
+
|
97 |
+
# PASS 3 (.equals(0))
|
98 |
+
# some expressions fail to simplify to zero, but
|
99 |
+
# ``.equals(0)`` evaluates to True. As a last-ditch
|
100 |
+
# attempt, apply ``.equals`` to these expressions
|
101 |
+
for i, x in enumerate(col):
|
102 |
+
if possible_zeros[i] is not None:
|
103 |
+
continue
|
104 |
+
if x.equals(S.Zero):
|
105 |
+
# ``.iszero`` may return False with
|
106 |
+
# an implicit assumption (e.g., ``x.equals(0)``
|
107 |
+
# when ``x`` is a symbol), so only treat it
|
108 |
+
# as proved when ``.equals(0)`` returns True
|
109 |
+
possible_zeros[i] = True
|
110 |
+
newly_determined.append((i, S.Zero))
|
111 |
+
|
112 |
+
if all(possible_zeros):
|
113 |
+
return (None, None, False, newly_determined)
|
114 |
+
|
115 |
+
# at this point there is nothing that could definitely
|
116 |
+
# be a pivot. To maintain compatibility with existing
|
117 |
+
# behavior, we'll assume that an illdetermined thing is
|
118 |
+
# non-zero. We should probably raise a warning in this case
|
119 |
+
i = possible_zeros.index(None)
|
120 |
+
return (i, col[i], True, newly_determined)
|
121 |
+
|
122 |
+
|
123 |
+
def _find_reasonable_pivot_naive(col, iszerofunc=_iszero, simpfunc=None):
|
124 |
+
"""
|
125 |
+
Helper that computes the pivot value and location from a
|
126 |
+
sequence of contiguous matrix column elements. As a side effect
|
127 |
+
of the pivot search, this function may simplify some of the elements
|
128 |
+
of the input column. A list of these simplified entries and their
|
129 |
+
indices are also returned.
|
130 |
+
This function mimics the behavior of _find_reasonable_pivot(),
|
131 |
+
but does less work trying to determine if an indeterminate candidate
|
132 |
+
pivot simplifies to zero. This more naive approach can be much faster,
|
133 |
+
with the trade-off that it may erroneously return a pivot that is zero.
|
134 |
+
|
135 |
+
``col`` is a sequence of contiguous column entries to be searched for
|
136 |
+
a suitable pivot.
|
137 |
+
``iszerofunc`` is a callable that returns a Boolean that indicates
|
138 |
+
if its input is zero, or None if no such determination can be made.
|
139 |
+
``simpfunc`` is a callable that simplifies its input. It must return
|
140 |
+
its input if it does not simplify its input. Passing in
|
141 |
+
``simpfunc=None`` indicates that the pivot search should not attempt
|
142 |
+
to simplify any candidate pivots.
|
143 |
+
|
144 |
+
Returns a 4-tuple:
|
145 |
+
(pivot_offset, pivot_val, assumed_nonzero, newly_determined)
|
146 |
+
``pivot_offset`` is the sequence index of the pivot.
|
147 |
+
``pivot_val`` is the value of the pivot.
|
148 |
+
pivot_val and col[pivot_index] are equivalent, but will be different
|
149 |
+
when col[pivot_index] was simplified during the pivot search.
|
150 |
+
``assumed_nonzero`` is a boolean indicating if the pivot cannot be
|
151 |
+
guaranteed to be zero. If assumed_nonzero is true, then the pivot
|
152 |
+
may or may not be non-zero. If assumed_nonzero is false, then
|
153 |
+
the pivot is non-zero.
|
154 |
+
``newly_determined`` is a list of index-value pairs of pivot candidates
|
155 |
+
that were simplified during the pivot search.
|
156 |
+
"""
|
157 |
+
|
158 |
+
# indeterminates holds the index-value pairs of each pivot candidate
|
159 |
+
# that is neither zero or non-zero, as determined by iszerofunc().
|
160 |
+
# If iszerofunc() indicates that a candidate pivot is guaranteed
|
161 |
+
# non-zero, or that every candidate pivot is zero then the contents
|
162 |
+
# of indeterminates are unused.
|
163 |
+
# Otherwise, the only viable candidate pivots are symbolic.
|
164 |
+
# In this case, indeterminates will have at least one entry,
|
165 |
+
# and all but the first entry are ignored when simpfunc is None.
|
166 |
+
indeterminates = []
|
167 |
+
for i, col_val in enumerate(col):
|
168 |
+
col_val_is_zero = iszerofunc(col_val)
|
169 |
+
if col_val_is_zero == False:
|
170 |
+
# This pivot candidate is non-zero.
|
171 |
+
return i, col_val, False, []
|
172 |
+
elif col_val_is_zero is None:
|
173 |
+
# The candidate pivot's comparison with zero
|
174 |
+
# is indeterminate.
|
175 |
+
indeterminates.append((i, col_val))
|
176 |
+
|
177 |
+
if len(indeterminates) == 0:
|
178 |
+
# All candidate pivots are guaranteed to be zero, i.e. there is
|
179 |
+
# no pivot.
|
180 |
+
return None, None, False, []
|
181 |
+
|
182 |
+
if simpfunc is None:
|
183 |
+
# Caller did not pass in a simplification function that might
|
184 |
+
# determine if an indeterminate pivot candidate is guaranteed
|
185 |
+
# to be nonzero, so assume the first indeterminate candidate
|
186 |
+
# is non-zero.
|
187 |
+
return indeterminates[0][0], indeterminates[0][1], True, []
|
188 |
+
|
189 |
+
# newly_determined holds index-value pairs of candidate pivots
|
190 |
+
# that were simplified during the search for a non-zero pivot.
|
191 |
+
newly_determined = []
|
192 |
+
for i, col_val in indeterminates:
|
193 |
+
tmp_col_val = simpfunc(col_val)
|
194 |
+
if id(col_val) != id(tmp_col_val):
|
195 |
+
# simpfunc() simplified this candidate pivot.
|
196 |
+
newly_determined.append((i, tmp_col_val))
|
197 |
+
if iszerofunc(tmp_col_val) == False:
|
198 |
+
# Candidate pivot simplified to a guaranteed non-zero value.
|
199 |
+
return i, tmp_col_val, False, newly_determined
|
200 |
+
|
201 |
+
return indeterminates[0][0], indeterminates[0][1], True, newly_determined
|
202 |
+
|
203 |
+
|
204 |
+
# This functions is a candidate for caching if it gets implemented for matrices.
|
205 |
+
def _berkowitz_toeplitz_matrix(M):
|
206 |
+
"""Return (A,T) where T the Toeplitz matrix used in the Berkowitz algorithm
|
207 |
+
corresponding to ``M`` and A is the first principal submatrix.
|
208 |
+
"""
|
209 |
+
|
210 |
+
# the 0 x 0 case is trivial
|
211 |
+
if M.rows == 0 and M.cols == 0:
|
212 |
+
return M._new(1,1, [M.one])
|
213 |
+
|
214 |
+
#
|
215 |
+
# Partition M = [ a_11 R ]
|
216 |
+
# [ C A ]
|
217 |
+
#
|
218 |
+
|
219 |
+
a, R = M[0,0], M[0, 1:]
|
220 |
+
C, A = M[1:, 0], M[1:,1:]
|
221 |
+
|
222 |
+
#
|
223 |
+
# The Toeplitz matrix looks like
|
224 |
+
#
|
225 |
+
# [ 1 ]
|
226 |
+
# [ -a 1 ]
|
227 |
+
# [ -RC -a 1 ]
|
228 |
+
# [ -RAC -RC -a 1 ]
|
229 |
+
# [ -RA**2C -RAC -RC -a 1 ]
|
230 |
+
# etc.
|
231 |
+
|
232 |
+
# Compute the diagonal entries.
|
233 |
+
# Because multiplying matrix times vector is so much
|
234 |
+
# more efficient than matrix times matrix, recursively
|
235 |
+
# compute -R * A**n * C.
|
236 |
+
diags = [C]
|
237 |
+
for i in range(M.rows - 2):
|
238 |
+
diags.append(A.multiply(diags[i], dotprodsimp=None))
|
239 |
+
diags = [(-R).multiply(d, dotprodsimp=None)[0, 0] for d in diags]
|
240 |
+
diags = [M.one, -a] + diags
|
241 |
+
|
242 |
+
def entry(i,j):
|
243 |
+
if j > i:
|
244 |
+
return M.zero
|
245 |
+
return diags[i - j]
|
246 |
+
|
247 |
+
toeplitz = M._new(M.cols + 1, M.rows, entry)
|
248 |
+
return (A, toeplitz)
|
249 |
+
|
250 |
+
|
251 |
+
# This functions is a candidate for caching if it gets implemented for matrices.
|
252 |
+
def _berkowitz_vector(M):
|
253 |
+
""" Run the Berkowitz algorithm and return a vector whose entries
|
254 |
+
are the coefficients of the characteristic polynomial of ``M``.
|
255 |
+
|
256 |
+
Given N x N matrix, efficiently compute
|
257 |
+
coefficients of characteristic polynomials of ``M``
|
258 |
+
without division in the ground domain.
|
259 |
+
|
260 |
+
This method is particularly useful for computing determinant,
|
261 |
+
principal minors and characteristic polynomial when ``M``
|
262 |
+
has complicated coefficients e.g. polynomials. Semi-direct
|
263 |
+
usage of this algorithm is also important in computing
|
264 |
+
efficiently sub-resultant PRS.
|
265 |
+
|
266 |
+
Assuming that M is a square matrix of dimension N x N and
|
267 |
+
I is N x N identity matrix, then the Berkowitz vector is
|
268 |
+
an N x 1 vector whose entries are coefficients of the
|
269 |
+
polynomial
|
270 |
+
|
271 |
+
charpoly(M) = det(t*I - M)
|
272 |
+
|
273 |
+
As a consequence, all polynomials generated by Berkowitz
|
274 |
+
algorithm are monic.
|
275 |
+
|
276 |
+
For more information on the implemented algorithm refer to:
|
277 |
+
|
278 |
+
[1] S.J. Berkowitz, On computing the determinant in small
|
279 |
+
parallel time using a small number of processors, ACM,
|
280 |
+
Information Processing Letters 18, 1984, pp. 147-150
|
281 |
+
|
282 |
+
[2] M. Keber, Division-Free computation of sub-resultants
|
283 |
+
using Bezout matrices, Tech. Report MPI-I-2006-1-006,
|
284 |
+
Saarbrucken, 2006
|
285 |
+
"""
|
286 |
+
|
287 |
+
# handle the trivial cases
|
288 |
+
if M.rows == 0 and M.cols == 0:
|
289 |
+
return M._new(1, 1, [M.one])
|
290 |
+
elif M.rows == 1 and M.cols == 1:
|
291 |
+
return M._new(2, 1, [M.one, -M[0,0]])
|
292 |
+
|
293 |
+
submat, toeplitz = _berkowitz_toeplitz_matrix(M)
|
294 |
+
|
295 |
+
return toeplitz.multiply(_berkowitz_vector(submat), dotprodsimp=None)
|
296 |
+
|
297 |
+
|
298 |
+
def _adjugate(M, method="berkowitz"):
|
299 |
+
"""Returns the adjugate, or classical adjoint, of
|
300 |
+
a matrix. That is, the transpose of the matrix of cofactors.
|
301 |
+
|
302 |
+
https://en.wikipedia.org/wiki/Adjugate
|
303 |
+
|
304 |
+
Parameters
|
305 |
+
==========
|
306 |
+
|
307 |
+
method : string, optional
|
308 |
+
Method to use to find the cofactors, can be "bareiss", "berkowitz",
|
309 |
+
"bird", "laplace" or "lu".
|
310 |
+
|
311 |
+
Examples
|
312 |
+
========
|
313 |
+
|
314 |
+
>>> from sympy import Matrix
|
315 |
+
>>> M = Matrix([[1, 2], [3, 4]])
|
316 |
+
>>> M.adjugate()
|
317 |
+
Matrix([
|
318 |
+
[ 4, -2],
|
319 |
+
[-3, 1]])
|
320 |
+
|
321 |
+
See Also
|
322 |
+
========
|
323 |
+
|
324 |
+
cofactor_matrix
|
325 |
+
sympy.matrices.matrixbase.MatrixBase.transpose
|
326 |
+
"""
|
327 |
+
|
328 |
+
return M.cofactor_matrix(method=method).transpose()
|
329 |
+
|
330 |
+
|
331 |
+
# This functions is a candidate for caching if it gets implemented for matrices.
|
332 |
+
def _charpoly(M, x='lambda', simplify=_simplify):
|
333 |
+
"""Computes characteristic polynomial det(x*I - M) where I is
|
334 |
+
the identity matrix.
|
335 |
+
|
336 |
+
A PurePoly is returned, so using different variables for ``x`` does
|
337 |
+
not affect the comparison or the polynomials:
|
338 |
+
|
339 |
+
Parameters
|
340 |
+
==========
|
341 |
+
|
342 |
+
x : string, optional
|
343 |
+
Name for the "lambda" variable, defaults to "lambda".
|
344 |
+
|
345 |
+
simplify : function, optional
|
346 |
+
Simplification function to use on the characteristic polynomial
|
347 |
+
calculated. Defaults to ``simplify``.
|
348 |
+
|
349 |
+
Examples
|
350 |
+
========
|
351 |
+
|
352 |
+
>>> from sympy import Matrix
|
353 |
+
>>> from sympy.abc import x, y
|
354 |
+
>>> M = Matrix([[1, 3], [2, 0]])
|
355 |
+
>>> M.charpoly()
|
356 |
+
PurePoly(lambda**2 - lambda - 6, lambda, domain='ZZ')
|
357 |
+
>>> M.charpoly(x) == M.charpoly(y)
|
358 |
+
True
|
359 |
+
>>> M.charpoly(x) == M.charpoly(y)
|
360 |
+
True
|
361 |
+
|
362 |
+
Specifying ``x`` is optional; a symbol named ``lambda`` is used by
|
363 |
+
default (which looks good when pretty-printed in unicode):
|
364 |
+
|
365 |
+
>>> M.charpoly().as_expr()
|
366 |
+
lambda**2 - lambda - 6
|
367 |
+
|
368 |
+
And if ``x`` clashes with an existing symbol, underscores will
|
369 |
+
be prepended to the name to make it unique:
|
370 |
+
|
371 |
+
>>> M = Matrix([[1, 2], [x, 0]])
|
372 |
+
>>> M.charpoly(x).as_expr()
|
373 |
+
_x**2 - _x - 2*x
|
374 |
+
|
375 |
+
Whether you pass a symbol or not, the generator can be obtained
|
376 |
+
with the gen attribute since it may not be the same as the symbol
|
377 |
+
that was passed:
|
378 |
+
|
379 |
+
>>> M.charpoly(x).gen
|
380 |
+
_x
|
381 |
+
>>> M.charpoly(x).gen == x
|
382 |
+
False
|
383 |
+
|
384 |
+
Notes
|
385 |
+
=====
|
386 |
+
|
387 |
+
The Samuelson-Berkowitz algorithm is used to compute
|
388 |
+
the characteristic polynomial efficiently and without any
|
389 |
+
division operations. Thus the characteristic polynomial over any
|
390 |
+
commutative ring without zero divisors can be computed.
|
391 |
+
|
392 |
+
If the determinant det(x*I - M) can be found out easily as
|
393 |
+
in the case of an upper or a lower triangular matrix, then
|
394 |
+
instead of Samuelson-Berkowitz algorithm, eigenvalues are computed
|
395 |
+
and the characteristic polynomial with their help.
|
396 |
+
|
397 |
+
See Also
|
398 |
+
========
|
399 |
+
|
400 |
+
det
|
401 |
+
"""
|
402 |
+
|
403 |
+
if not M.is_square:
|
404 |
+
raise NonSquareMatrixError()
|
405 |
+
|
406 |
+
# Use DomainMatrix. We are already going to convert this to a Poly so there
|
407 |
+
# is no need to worry about expanding powers etc. Also since this algorithm
|
408 |
+
# does not require division or zero detection it is fine to use EX.
|
409 |
+
#
|
410 |
+
# M.to_DM() will fall back on EXRAW rather than EX. EXRAW is a lot faster
|
411 |
+
# for elementary arithmetic because it does not call cancel for each
|
412 |
+
# operation but it generates large unsimplified results that are slow in
|
413 |
+
# the subsequent call to simplify. Using EX instead is faster overall
|
414 |
+
# but at least in some cases EXRAW+simplify gives a simpler result so we
|
415 |
+
# preserve that existing behaviour of charpoly for now...
|
416 |
+
dM = M.to_DM()
|
417 |
+
|
418 |
+
K = dM.domain
|
419 |
+
|
420 |
+
cp = dM.charpoly()
|
421 |
+
|
422 |
+
x = uniquely_named_symbol(x, [M], modify=lambda s: '_' + s)
|
423 |
+
|
424 |
+
if K.is_EXRAW or simplify is not _simplify:
|
425 |
+
# XXX: Converting back to Expr is expensive. We only do it if the
|
426 |
+
# caller supplied a custom simplify function for backwards
|
427 |
+
# compatibility or otherwise if the domain was EX. For any other domain
|
428 |
+
# there should be no benefit in simplifying at this stage because Poly
|
429 |
+
# will put everything into canonical form anyway.
|
430 |
+
berk_vector = [K.to_sympy(c) for c in cp]
|
431 |
+
berk_vector = [simplify(a) for a in berk_vector]
|
432 |
+
p = PurePoly(berk_vector, x)
|
433 |
+
|
434 |
+
else:
|
435 |
+
# Convert from the list of domain elements directly to Poly.
|
436 |
+
p = PurePoly(cp, x, domain=K)
|
437 |
+
|
438 |
+
return p
|
439 |
+
|
440 |
+
|
441 |
+
def _cofactor(M, i, j, method="berkowitz"):
|
442 |
+
"""Calculate the cofactor of an element.
|
443 |
+
|
444 |
+
Parameters
|
445 |
+
==========
|
446 |
+
|
447 |
+
method : string, optional
|
448 |
+
Method to use to find the cofactors, can be "bareiss", "berkowitz",
|
449 |
+
"bird", "laplace" or "lu".
|
450 |
+
|
451 |
+
Examples
|
452 |
+
========
|
453 |
+
|
454 |
+
>>> from sympy import Matrix
|
455 |
+
>>> M = Matrix([[1, 2], [3, 4]])
|
456 |
+
>>> M.cofactor(0, 1)
|
457 |
+
-3
|
458 |
+
|
459 |
+
See Also
|
460 |
+
========
|
461 |
+
|
462 |
+
cofactor_matrix
|
463 |
+
minor
|
464 |
+
minor_submatrix
|
465 |
+
"""
|
466 |
+
|
467 |
+
if not M.is_square or M.rows < 1:
|
468 |
+
raise NonSquareMatrixError()
|
469 |
+
|
470 |
+
return S.NegativeOne**((i + j) % 2) * M.minor(i, j, method)
|
471 |
+
|
472 |
+
|
473 |
+
def _cofactor_matrix(M, method="berkowitz"):
|
474 |
+
"""Return a matrix containing the cofactor of each element.
|
475 |
+
|
476 |
+
Parameters
|
477 |
+
==========
|
478 |
+
|
479 |
+
method : string, optional
|
480 |
+
Method to use to find the cofactors, can be "bareiss", "berkowitz",
|
481 |
+
"bird", "laplace" or "lu".
|
482 |
+
|
483 |
+
Examples
|
484 |
+
========
|
485 |
+
|
486 |
+
>>> from sympy import Matrix
|
487 |
+
>>> M = Matrix([[1, 2], [3, 4]])
|
488 |
+
>>> M.cofactor_matrix()
|
489 |
+
Matrix([
|
490 |
+
[ 4, -3],
|
491 |
+
[-2, 1]])
|
492 |
+
|
493 |
+
See Also
|
494 |
+
========
|
495 |
+
|
496 |
+
cofactor
|
497 |
+
minor
|
498 |
+
minor_submatrix
|
499 |
+
"""
|
500 |
+
|
501 |
+
if not M.is_square:
|
502 |
+
raise NonSquareMatrixError()
|
503 |
+
|
504 |
+
return M._new(M.rows, M.cols,
|
505 |
+
lambda i, j: M.cofactor(i, j, method))
|
506 |
+
|
507 |
+
def _per(M):
|
508 |
+
"""Returns the permanent of a matrix. Unlike determinant,
|
509 |
+
permanent is defined for both square and non-square matrices.
|
510 |
+
|
511 |
+
For an m x n matrix, with m less than or equal to n,
|
512 |
+
it is given as the sum over the permutations s of size
|
513 |
+
less than or equal to m on [1, 2, . . . n] of the product
|
514 |
+
from i = 1 to m of M[i, s[i]]. Taking the transpose will
|
515 |
+
not affect the value of the permanent.
|
516 |
+
|
517 |
+
In the case of a square matrix, this is the same as the permutation
|
518 |
+
definition of the determinant, but it does not take the sign of the
|
519 |
+
permutation into account. Computing the permanent with this definition
|
520 |
+
is quite inefficient, so here the Ryser formula is used.
|
521 |
+
|
522 |
+
Examples
|
523 |
+
========
|
524 |
+
|
525 |
+
>>> from sympy import Matrix
|
526 |
+
>>> M = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
|
527 |
+
>>> M.per()
|
528 |
+
450
|
529 |
+
>>> M = Matrix([1, 5, 7])
|
530 |
+
>>> M.per()
|
531 |
+
13
|
532 |
+
|
533 |
+
References
|
534 |
+
==========
|
535 |
+
|
536 |
+
.. [1] Prof. Frank Ben's notes: https://math.berkeley.edu/~bernd/ban275.pdf
|
537 |
+
.. [2] Wikipedia article on Permanent: https://en.wikipedia.org/wiki/Permanent_%28mathematics%29
|
538 |
+
.. [3] https://reference.wolfram.com/language/ref/Permanent.html
|
539 |
+
.. [4] Permanent of a rectangular matrix : https://arxiv.org/pdf/0904.3251.pdf
|
540 |
+
"""
|
541 |
+
import itertools
|
542 |
+
|
543 |
+
m, n = M.shape
|
544 |
+
if m > n:
|
545 |
+
M = M.T
|
546 |
+
m, n = n, m
|
547 |
+
s = list(range(n))
|
548 |
+
|
549 |
+
subsets = []
|
550 |
+
for i in range(1, m + 1):
|
551 |
+
subsets += list(map(list, itertools.combinations(s, i)))
|
552 |
+
|
553 |
+
perm = 0
|
554 |
+
for subset in subsets:
|
555 |
+
prod = 1
|
556 |
+
sub_len = len(subset)
|
557 |
+
for i in range(m):
|
558 |
+
prod *= sum(M[i, j] for j in subset)
|
559 |
+
perm += prod * S.NegativeOne**sub_len * nC(n - sub_len, m - sub_len)
|
560 |
+
perm *= S.NegativeOne**m
|
561 |
+
return perm.simplify()
|
562 |
+
|
563 |
+
def _det_DOM(M):
|
564 |
+
DOM = DomainMatrix.from_Matrix(M, field=True, extension=True)
|
565 |
+
K = DOM.domain
|
566 |
+
return K.to_sympy(DOM.det())
|
567 |
+
|
568 |
+
# This functions is a candidate for caching if it gets implemented for matrices.
|
569 |
+
def _det(M, method="bareiss", iszerofunc=None):
|
570 |
+
"""Computes the determinant of a matrix if ``M`` is a concrete matrix object
|
571 |
+
otherwise return an expressions ``Determinant(M)`` if ``M`` is a
|
572 |
+
``MatrixSymbol`` or other expression.
|
573 |
+
|
574 |
+
Parameters
|
575 |
+
==========
|
576 |
+
|
577 |
+
method : string, optional
|
578 |
+
Specifies the algorithm used for computing the matrix determinant.
|
579 |
+
|
580 |
+
If the matrix is at most 3x3, a hard-coded formula is used and the
|
581 |
+
specified method is ignored. Otherwise, it defaults to
|
582 |
+
``'bareiss'``.
|
583 |
+
|
584 |
+
Also, if the matrix is an upper or a lower triangular matrix, determinant
|
585 |
+
is computed by simple multiplication of diagonal elements, and the
|
586 |
+
specified method is ignored.
|
587 |
+
|
588 |
+
If it is set to ``'domain-ge'``, then Gaussian elimination method will
|
589 |
+
be used via using DomainMatrix.
|
590 |
+
|
591 |
+
If it is set to ``'bareiss'``, Bareiss' fraction-free algorithm will
|
592 |
+
be used.
|
593 |
+
|
594 |
+
If it is set to ``'berkowitz'``, Berkowitz' algorithm will be used.
|
595 |
+
|
596 |
+
If it is set to ``'bird'``, Bird's algorithm will be used [1]_.
|
597 |
+
|
598 |
+
If it is set to ``'laplace'``, Laplace's algorithm will be used.
|
599 |
+
|
600 |
+
Otherwise, if it is set to ``'lu'``, LU decomposition will be used.
|
601 |
+
|
602 |
+
.. note::
|
603 |
+
For backward compatibility, legacy keys like "bareis" and
|
604 |
+
"det_lu" can still be used to indicate the corresponding
|
605 |
+
methods.
|
606 |
+
And the keys are also case-insensitive for now. However, it is
|
607 |
+
suggested to use the precise keys for specifying the method.
|
608 |
+
|
609 |
+
iszerofunc : FunctionType or None, optional
|
610 |
+
If it is set to ``None``, it will be defaulted to ``_iszero`` if the
|
611 |
+
method is set to ``'bareiss'``, and ``_is_zero_after_expand_mul`` if
|
612 |
+
the method is set to ``'lu'``.
|
613 |
+
|
614 |
+
It can also accept any user-specified zero testing function, if it
|
615 |
+
is formatted as a function which accepts a single symbolic argument
|
616 |
+
and returns ``True`` if it is tested as zero and ``False`` if it
|
617 |
+
tested as non-zero, and also ``None`` if it is undecidable.
|
618 |
+
|
619 |
+
Returns
|
620 |
+
=======
|
621 |
+
|
622 |
+
det : Basic
|
623 |
+
Result of determinant.
|
624 |
+
|
625 |
+
Raises
|
626 |
+
======
|
627 |
+
|
628 |
+
ValueError
|
629 |
+
If unrecognized keys are given for ``method`` or ``iszerofunc``.
|
630 |
+
|
631 |
+
NonSquareMatrixError
|
632 |
+
If attempted to calculate determinant from a non-square matrix.
|
633 |
+
|
634 |
+
Examples
|
635 |
+
========
|
636 |
+
|
637 |
+
>>> from sympy import Matrix, eye, det
|
638 |
+
>>> I3 = eye(3)
|
639 |
+
>>> det(I3)
|
640 |
+
1
|
641 |
+
>>> M = Matrix([[1, 2], [3, 4]])
|
642 |
+
>>> det(M)
|
643 |
+
-2
|
644 |
+
>>> det(M) == M.det()
|
645 |
+
True
|
646 |
+
>>> M.det(method="domain-ge")
|
647 |
+
-2
|
648 |
+
|
649 |
+
References
|
650 |
+
==========
|
651 |
+
|
652 |
+
.. [1] Bird, R. S. (2011). A simple division-free algorithm for computing
|
653 |
+
determinants. Inf. Process. Lett., 111(21), 1072-1074. doi:
|
654 |
+
10.1016/j.ipl.2011.08.006
|
655 |
+
"""
|
656 |
+
|
657 |
+
# sanitize `method`
|
658 |
+
method = method.lower()
|
659 |
+
|
660 |
+
if method == "bareis":
|
661 |
+
method = "bareiss"
|
662 |
+
elif method == "det_lu":
|
663 |
+
method = "lu"
|
664 |
+
|
665 |
+
if method not in ("bareiss", "berkowitz", "lu", "domain-ge", "bird",
|
666 |
+
"laplace"):
|
667 |
+
raise ValueError("Determinant method '%s' unrecognized" % method)
|
668 |
+
|
669 |
+
if iszerofunc is None:
|
670 |
+
if method == "bareiss":
|
671 |
+
iszerofunc = _is_zero_after_expand_mul
|
672 |
+
elif method == "lu":
|
673 |
+
iszerofunc = _iszero
|
674 |
+
|
675 |
+
elif not isinstance(iszerofunc, FunctionType):
|
676 |
+
raise ValueError("Zero testing method '%s' unrecognized" % iszerofunc)
|
677 |
+
|
678 |
+
n = M.rows
|
679 |
+
|
680 |
+
if n == M.cols: # square check is done in individual method functions
|
681 |
+
if n == 0:
|
682 |
+
return M.one
|
683 |
+
elif n == 1:
|
684 |
+
return M[0, 0]
|
685 |
+
elif n == 2:
|
686 |
+
m = M[0, 0] * M[1, 1] - M[0, 1] * M[1, 0]
|
687 |
+
return _get_intermediate_simp(_dotprodsimp)(m)
|
688 |
+
elif n == 3:
|
689 |
+
m = (M[0, 0] * M[1, 1] * M[2, 2]
|
690 |
+
+ M[0, 1] * M[1, 2] * M[2, 0]
|
691 |
+
+ M[0, 2] * M[1, 0] * M[2, 1]
|
692 |
+
- M[0, 2] * M[1, 1] * M[2, 0]
|
693 |
+
- M[0, 0] * M[1, 2] * M[2, 1]
|
694 |
+
- M[0, 1] * M[1, 0] * M[2, 2])
|
695 |
+
return _get_intermediate_simp(_dotprodsimp)(m)
|
696 |
+
|
697 |
+
dets = []
|
698 |
+
for b in M.strongly_connected_components():
|
699 |
+
if method == "domain-ge": # uses DomainMatrix to evaluate determinant
|
700 |
+
det = _det_DOM(M[b, b])
|
701 |
+
elif method == "bareiss":
|
702 |
+
det = M[b, b]._eval_det_bareiss(iszerofunc=iszerofunc)
|
703 |
+
elif method == "berkowitz":
|
704 |
+
det = M[b, b]._eval_det_berkowitz()
|
705 |
+
elif method == "lu":
|
706 |
+
det = M[b, b]._eval_det_lu(iszerofunc=iszerofunc)
|
707 |
+
elif method == "bird":
|
708 |
+
det = M[b, b]._eval_det_bird()
|
709 |
+
elif method == "laplace":
|
710 |
+
det = M[b, b]._eval_det_laplace()
|
711 |
+
dets.append(det)
|
712 |
+
return Mul(*dets)
|
713 |
+
|
714 |
+
|
715 |
+
# This functions is a candidate for caching if it gets implemented for matrices.
|
716 |
+
def _det_bareiss(M, iszerofunc=_is_zero_after_expand_mul):
|
717 |
+
"""Compute matrix determinant using Bareiss' fraction-free
|
718 |
+
algorithm which is an extension of the well known Gaussian
|
719 |
+
elimination method. This approach is best suited for dense
|
720 |
+
symbolic matrices and will result in a determinant with
|
721 |
+
minimal number of fractions. It means that less term
|
722 |
+
rewriting is needed on resulting formulae.
|
723 |
+
|
724 |
+
Parameters
|
725 |
+
==========
|
726 |
+
|
727 |
+
iszerofunc : function, optional
|
728 |
+
The function to use to determine zeros when doing an LU decomposition.
|
729 |
+
Defaults to ``lambda x: x.is_zero``.
|
730 |
+
|
731 |
+
TODO: Implement algorithm for sparse matrices (SFF),
|
732 |
+
http://www.eecis.udel.edu/~saunders/papers/sffge/it5.ps.
|
733 |
+
"""
|
734 |
+
|
735 |
+
# Recursively implemented Bareiss' algorithm as per Deanna Richelle Leggett's
|
736 |
+
# thesis http://www.math.usm.edu/perry/Research/Thesis_DRL.pdf
|
737 |
+
def bareiss(mat, cumm=1):
|
738 |
+
if mat.rows == 0:
|
739 |
+
return mat.one
|
740 |
+
elif mat.rows == 1:
|
741 |
+
return mat[0, 0]
|
742 |
+
|
743 |
+
# find a pivot and extract the remaining matrix
|
744 |
+
# With the default iszerofunc, _find_reasonable_pivot slows down
|
745 |
+
# the computation by the factor of 2.5 in one test.
|
746 |
+
# Relevant issues: #10279 and #13877.
|
747 |
+
pivot_pos, pivot_val, _, _ = _find_reasonable_pivot(mat[:, 0], iszerofunc=iszerofunc)
|
748 |
+
if pivot_pos is None:
|
749 |
+
return mat.zero
|
750 |
+
|
751 |
+
# if we have a valid pivot, we'll do a "row swap", so keep the
|
752 |
+
# sign of the det
|
753 |
+
sign = (-1) ** (pivot_pos % 2)
|
754 |
+
|
755 |
+
# we want every row but the pivot row and every column
|
756 |
+
rows = [i for i in range(mat.rows) if i != pivot_pos]
|
757 |
+
cols = list(range(mat.cols))
|
758 |
+
tmp_mat = mat.extract(rows, cols)
|
759 |
+
|
760 |
+
def entry(i, j):
|
761 |
+
ret = (pivot_val*tmp_mat[i, j + 1] - mat[pivot_pos, j + 1]*tmp_mat[i, 0]) / cumm
|
762 |
+
if _get_intermediate_simp_bool(True):
|
763 |
+
return _dotprodsimp(ret)
|
764 |
+
elif not ret.is_Atom:
|
765 |
+
return cancel(ret)
|
766 |
+
return ret
|
767 |
+
|
768 |
+
return sign*bareiss(M._new(mat.rows - 1, mat.cols - 1, entry), pivot_val)
|
769 |
+
|
770 |
+
if not M.is_square:
|
771 |
+
raise NonSquareMatrixError()
|
772 |
+
|
773 |
+
if M.rows == 0:
|
774 |
+
return M.one
|
775 |
+
# sympy/matrices/tests/test_matrices.py contains a test that
|
776 |
+
# suggests that the determinant of a 0 x 0 matrix is one, by
|
777 |
+
# convention.
|
778 |
+
|
779 |
+
return bareiss(M)
|
780 |
+
|
781 |
+
|
782 |
+
def _det_berkowitz(M):
|
783 |
+
""" Use the Berkowitz algorithm to compute the determinant."""
|
784 |
+
|
785 |
+
if not M.is_square:
|
786 |
+
raise NonSquareMatrixError()
|
787 |
+
|
788 |
+
if M.rows == 0:
|
789 |
+
return M.one
|
790 |
+
# sympy/matrices/tests/test_matrices.py contains a test that
|
791 |
+
# suggests that the determinant of a 0 x 0 matrix is one, by
|
792 |
+
# convention.
|
793 |
+
|
794 |
+
berk_vector = _berkowitz_vector(M)
|
795 |
+
return (-1)**(len(berk_vector) - 1) * berk_vector[-1]
|
796 |
+
|
797 |
+
|
798 |
+
# This functions is a candidate for caching if it gets implemented for matrices.
|
799 |
+
def _det_LU(M, iszerofunc=_iszero, simpfunc=None):
|
800 |
+
""" Computes the determinant of a matrix from its LU decomposition.
|
801 |
+
This function uses the LU decomposition computed by
|
802 |
+
LUDecomposition_Simple().
|
803 |
+
|
804 |
+
The keyword arguments iszerofunc and simpfunc are passed to
|
805 |
+
LUDecomposition_Simple().
|
806 |
+
iszerofunc is a callable that returns a boolean indicating if its
|
807 |
+
input is zero, or None if it cannot make the determination.
|
808 |
+
simpfunc is a callable that simplifies its input.
|
809 |
+
The default is simpfunc=None, which indicate that the pivot search
|
810 |
+
algorithm should not attempt to simplify any candidate pivots.
|
811 |
+
If simpfunc fails to simplify its input, then it must return its input
|
812 |
+
instead of a copy.
|
813 |
+
|
814 |
+
Parameters
|
815 |
+
==========
|
816 |
+
|
817 |
+
iszerofunc : function, optional
|
818 |
+
The function to use to determine zeros when doing an LU decomposition.
|
819 |
+
Defaults to ``lambda x: x.is_zero``.
|
820 |
+
|
821 |
+
simpfunc : function, optional
|
822 |
+
The simplification function to use when looking for zeros for pivots.
|
823 |
+
"""
|
824 |
+
|
825 |
+
if not M.is_square:
|
826 |
+
raise NonSquareMatrixError()
|
827 |
+
|
828 |
+
if M.rows == 0:
|
829 |
+
return M.one
|
830 |
+
# sympy/matrices/tests/test_matrices.py contains a test that
|
831 |
+
# suggests that the determinant of a 0 x 0 matrix is one, by
|
832 |
+
# convention.
|
833 |
+
|
834 |
+
lu, row_swaps = M.LUdecomposition_Simple(iszerofunc=iszerofunc,
|
835 |
+
simpfunc=simpfunc)
|
836 |
+
# P*A = L*U => det(A) = det(L)*det(U)/det(P) = det(P)*det(U).
|
837 |
+
# Lower triangular factor L encoded in lu has unit diagonal => det(L) = 1.
|
838 |
+
# P is a permutation matrix => det(P) in {-1, 1} => 1/det(P) = det(P).
|
839 |
+
# LUdecomposition_Simple() returns a list of row exchange index pairs, rather
|
840 |
+
# than a permutation matrix, but det(P) = (-1)**len(row_swaps).
|
841 |
+
|
842 |
+
# Avoid forming the potentially time consuming product of U's diagonal entries
|
843 |
+
# if the product is zero.
|
844 |
+
# Bottom right entry of U is 0 => det(A) = 0.
|
845 |
+
# It may be impossible to determine if this entry of U is zero when it is symbolic.
|
846 |
+
if iszerofunc(lu[lu.rows-1, lu.rows-1]):
|
847 |
+
return M.zero
|
848 |
+
|
849 |
+
# Compute det(P)
|
850 |
+
det = -M.one if len(row_swaps)%2 else M.one
|
851 |
+
|
852 |
+
# Compute det(U) by calculating the product of U's diagonal entries.
|
853 |
+
# The upper triangular portion of lu is the upper triangular portion of the
|
854 |
+
# U factor in the LU decomposition.
|
855 |
+
for k in range(lu.rows):
|
856 |
+
det *= lu[k, k]
|
857 |
+
|
858 |
+
# return det(P)*det(U)
|
859 |
+
return det
|
860 |
+
|
861 |
+
|
862 |
+
@cacheit
|
863 |
+
def __det_laplace(M):
|
864 |
+
"""Compute the determinant of a matrix using Laplace expansion.
|
865 |
+
|
866 |
+
This is a recursive function, and it should not be called directly.
|
867 |
+
Use _det_laplace() instead. The reason for splitting this function
|
868 |
+
into two is to allow caching of determinants of submatrices. While
|
869 |
+
one could also define this function inside _det_laplace(), that
|
870 |
+
would remove the advantage of using caching in Cramer Solve.
|
871 |
+
"""
|
872 |
+
n = M.shape[0]
|
873 |
+
if n == 1:
|
874 |
+
return M[0]
|
875 |
+
elif n == 2:
|
876 |
+
return M[0, 0] * M[1, 1] - M[0, 1] * M[1, 0]
|
877 |
+
else:
|
878 |
+
return sum((-1) ** i * M[0, i] *
|
879 |
+
__det_laplace(M.minor_submatrix(0, i)) for i in range(n))
|
880 |
+
|
881 |
+
|
882 |
+
def _det_laplace(M):
|
883 |
+
"""Compute the determinant of a matrix using Laplace expansion.
|
884 |
+
|
885 |
+
While Laplace expansion is not the most efficient method of computing
|
886 |
+
a determinant, it is a simple one, and it has the advantage of
|
887 |
+
being division free. To improve efficiency, this function uses
|
888 |
+
caching to avoid recomputing determinants of submatrices.
|
889 |
+
"""
|
890 |
+
if not M.is_square:
|
891 |
+
raise NonSquareMatrixError()
|
892 |
+
if M.shape[0] == 0:
|
893 |
+
return M.one
|
894 |
+
# sympy/matrices/tests/test_matrices.py contains a test that
|
895 |
+
# suggests that the determinant of a 0 x 0 matrix is one, by
|
896 |
+
# convention.
|
897 |
+
return __det_laplace(M.as_immutable())
|
898 |
+
|
899 |
+
|
900 |
+
def _det_bird(M):
|
901 |
+
r"""Compute the determinant of a matrix using Bird's algorithm.
|
902 |
+
|
903 |
+
Bird's algorithm is a simple division-free algorithm for computing, which
|
904 |
+
is of lower order than the Laplace's algorithm. It is described in [1]_.
|
905 |
+
|
906 |
+
References
|
907 |
+
==========
|
908 |
+
|
909 |
+
.. [1] Bird, R. S. (2011). A simple division-free algorithm for computing
|
910 |
+
determinants. Inf. Process. Lett., 111(21), 1072-1074. doi:
|
911 |
+
10.1016/j.ipl.2011.08.006
|
912 |
+
"""
|
913 |
+
def mu(X):
|
914 |
+
n = X.shape[0]
|
915 |
+
zero = X.domain.zero
|
916 |
+
|
917 |
+
total = zero
|
918 |
+
diag_sums = [zero]
|
919 |
+
for i in reversed(range(1, n)):
|
920 |
+
total -= X[i][i]
|
921 |
+
diag_sums.append(total)
|
922 |
+
diag_sums = diag_sums[::-1]
|
923 |
+
|
924 |
+
elems = [[zero] * i + [diag_sums[i]] + X_i[i + 1:] for i, X_i in
|
925 |
+
enumerate(X)]
|
926 |
+
return DDM(elems, X.shape, X.domain)
|
927 |
+
|
928 |
+
Mddm = M._rep.to_ddm()
|
929 |
+
n = M.shape[0]
|
930 |
+
if n == 0:
|
931 |
+
return M.one
|
932 |
+
# sympy/matrices/tests/test_matrices.py contains a test that
|
933 |
+
# suggests that the determinant of a 0 x 0 matrix is one, by
|
934 |
+
# convention.
|
935 |
+
Fn1 = Mddm
|
936 |
+
for _ in range(n - 1):
|
937 |
+
Fn1 = mu(Fn1).matmul(Mddm)
|
938 |
+
detA = Fn1[0][0]
|
939 |
+
if n % 2 == 0:
|
940 |
+
detA = -detA
|
941 |
+
|
942 |
+
return Mddm.domain.to_sympy(detA)
|
943 |
+
|
944 |
+
|
945 |
+
def _minor(M, i, j, method="berkowitz"):
|
946 |
+
"""Return the (i,j) minor of ``M``. That is,
|
947 |
+
return the determinant of the matrix obtained by deleting
|
948 |
+
the `i`th row and `j`th column from ``M``.
|
949 |
+
|
950 |
+
Parameters
|
951 |
+
==========
|
952 |
+
|
953 |
+
i, j : int
|
954 |
+
The row and column to exclude to obtain the submatrix.
|
955 |
+
|
956 |
+
method : string, optional
|
957 |
+
Method to use to find the determinant of the submatrix, can be
|
958 |
+
"bareiss", "berkowitz", "bird", "laplace" or "lu".
|
959 |
+
|
960 |
+
Examples
|
961 |
+
========
|
962 |
+
|
963 |
+
>>> from sympy import Matrix
|
964 |
+
>>> M = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
|
965 |
+
>>> M.minor(1, 1)
|
966 |
+
-12
|
967 |
+
|
968 |
+
See Also
|
969 |
+
========
|
970 |
+
|
971 |
+
minor_submatrix
|
972 |
+
cofactor
|
973 |
+
det
|
974 |
+
"""
|
975 |
+
|
976 |
+
if not M.is_square:
|
977 |
+
raise NonSquareMatrixError()
|
978 |
+
|
979 |
+
return M.minor_submatrix(i, j).det(method=method)
|
980 |
+
|
981 |
+
|
982 |
+
def _minor_submatrix(M, i, j):
|
983 |
+
"""Return the submatrix obtained by removing the `i`th row
|
984 |
+
and `j`th column from ``M`` (works with Pythonic negative indices).
|
985 |
+
|
986 |
+
Parameters
|
987 |
+
==========
|
988 |
+
|
989 |
+
i, j : int
|
990 |
+
The row and column to exclude to obtain the submatrix.
|
991 |
+
|
992 |
+
Examples
|
993 |
+
========
|
994 |
+
|
995 |
+
>>> from sympy import Matrix
|
996 |
+
>>> M = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
|
997 |
+
>>> M.minor_submatrix(1, 1)
|
998 |
+
Matrix([
|
999 |
+
[1, 3],
|
1000 |
+
[7, 9]])
|
1001 |
+
|
1002 |
+
See Also
|
1003 |
+
========
|
1004 |
+
|
1005 |
+
minor
|
1006 |
+
cofactor
|
1007 |
+
"""
|
1008 |
+
|
1009 |
+
if i < 0:
|
1010 |
+
i += M.rows
|
1011 |
+
if j < 0:
|
1012 |
+
j += M.cols
|
1013 |
+
|
1014 |
+
if not 0 <= i < M.rows or not 0 <= j < M.cols:
|
1015 |
+
raise ValueError("`i` and `j` must satisfy 0 <= i < ``M.rows`` "
|
1016 |
+
"(%d)" % M.rows + "and 0 <= j < ``M.cols`` (%d)." % M.cols)
|
1017 |
+
|
1018 |
+
rows = [a for a in range(M.rows) if a != i]
|
1019 |
+
cols = [a for a in range(M.cols) if a != j]
|
1020 |
+
|
1021 |
+
return M.extract(rows, cols)
|
.venv/lib/python3.11/site-packages/sympy/matrices/eigen.py
ADDED
@@ -0,0 +1,1346 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from types import FunctionType
|
2 |
+
from collections import Counter
|
3 |
+
|
4 |
+
from mpmath import mp, workprec
|
5 |
+
from mpmath.libmp.libmpf import prec_to_dps
|
6 |
+
|
7 |
+
from sympy.core.sorting import default_sort_key
|
8 |
+
from sympy.core.evalf import DEFAULT_MAXPREC, PrecisionExhausted
|
9 |
+
from sympy.core.logic import fuzzy_and, fuzzy_or
|
10 |
+
from sympy.core.numbers import Float
|
11 |
+
from sympy.core.sympify import _sympify
|
12 |
+
from sympy.functions.elementary.miscellaneous import sqrt
|
13 |
+
from sympy.polys import roots, CRootOf, ZZ, QQ, EX
|
14 |
+
from sympy.polys.matrices import DomainMatrix
|
15 |
+
from sympy.polys.matrices.eigen import dom_eigenvects, dom_eigenvects_to_sympy
|
16 |
+
from sympy.polys.polytools import gcd
|
17 |
+
|
18 |
+
from .exceptions import MatrixError, NonSquareMatrixError
|
19 |
+
from .determinant import _find_reasonable_pivot
|
20 |
+
|
21 |
+
from .utilities import _iszero, _simplify
|
22 |
+
|
23 |
+
|
24 |
+
__doctest_requires__ = {
|
25 |
+
('_is_indefinite',
|
26 |
+
'_is_negative_definite',
|
27 |
+
'_is_negative_semidefinite',
|
28 |
+
'_is_positive_definite',
|
29 |
+
'_is_positive_semidefinite'): ['matplotlib'],
|
30 |
+
}
|
31 |
+
|
32 |
+
|
33 |
+
def _eigenvals_eigenvects_mpmath(M):
|
34 |
+
norm2 = lambda v: mp.sqrt(sum(i**2 for i in v))
|
35 |
+
|
36 |
+
v1 = None
|
37 |
+
prec = max(x._prec for x in M.atoms(Float))
|
38 |
+
eps = 2**-prec
|
39 |
+
|
40 |
+
while prec < DEFAULT_MAXPREC:
|
41 |
+
with workprec(prec):
|
42 |
+
A = mp.matrix(M.evalf(n=prec_to_dps(prec)))
|
43 |
+
E, ER = mp.eig(A)
|
44 |
+
v2 = norm2([i for e in E for i in (mp.re(e), mp.im(e))])
|
45 |
+
if v1 is not None and mp.fabs(v1 - v2) < eps:
|
46 |
+
return E, ER
|
47 |
+
v1 = v2
|
48 |
+
prec *= 2
|
49 |
+
|
50 |
+
# we get here because the next step would have taken us
|
51 |
+
# past MAXPREC or because we never took a step; in case
|
52 |
+
# of the latter, we refuse to send back a solution since
|
53 |
+
# it would not have been verified; we also resist taking
|
54 |
+
# a small step to arrive exactly at MAXPREC since then
|
55 |
+
# the two calculations might be artificially close.
|
56 |
+
raise PrecisionExhausted
|
57 |
+
|
58 |
+
|
59 |
+
def _eigenvals_mpmath(M, multiple=False):
|
60 |
+
"""Compute eigenvalues using mpmath"""
|
61 |
+
E, _ = _eigenvals_eigenvects_mpmath(M)
|
62 |
+
result = [_sympify(x) for x in E]
|
63 |
+
if multiple:
|
64 |
+
return result
|
65 |
+
return dict(Counter(result))
|
66 |
+
|
67 |
+
|
68 |
+
def _eigenvects_mpmath(M):
|
69 |
+
E, ER = _eigenvals_eigenvects_mpmath(M)
|
70 |
+
result = []
|
71 |
+
for i in range(M.rows):
|
72 |
+
eigenval = _sympify(E[i])
|
73 |
+
eigenvect = _sympify(ER[:, i])
|
74 |
+
result.append((eigenval, 1, [eigenvect]))
|
75 |
+
|
76 |
+
return result
|
77 |
+
|
78 |
+
|
79 |
+
# This function is a candidate for caching if it gets implemented for matrices.
|
80 |
+
def _eigenvals(
|
81 |
+
M, error_when_incomplete=True, *, simplify=False, multiple=False,
|
82 |
+
rational=False, **flags):
|
83 |
+
r"""Compute eigenvalues of the matrix.
|
84 |
+
|
85 |
+
Parameters
|
86 |
+
==========
|
87 |
+
|
88 |
+
error_when_incomplete : bool, optional
|
89 |
+
If it is set to ``True``, it will raise an error if not all
|
90 |
+
eigenvalues are computed. This is caused by ``roots`` not returning
|
91 |
+
a full list of eigenvalues.
|
92 |
+
|
93 |
+
simplify : bool or function, optional
|
94 |
+
If it is set to ``True``, it attempts to return the most
|
95 |
+
simplified form of expressions returned by applying default
|
96 |
+
simplification method in every routine.
|
97 |
+
|
98 |
+
If it is set to ``False``, it will skip simplification in this
|
99 |
+
particular routine to save computation resources.
|
100 |
+
|
101 |
+
If a function is passed to, it will attempt to apply
|
102 |
+
the particular function as simplification method.
|
103 |
+
|
104 |
+
rational : bool, optional
|
105 |
+
If it is set to ``True``, every floating point numbers would be
|
106 |
+
replaced with rationals before computation. It can solve some
|
107 |
+
issues of ``roots`` routine not working well with floats.
|
108 |
+
|
109 |
+
multiple : bool, optional
|
110 |
+
If it is set to ``True``, the result will be in the form of a
|
111 |
+
list.
|
112 |
+
|
113 |
+
If it is set to ``False``, the result will be in the form of a
|
114 |
+
dictionary.
|
115 |
+
|
116 |
+
Returns
|
117 |
+
=======
|
118 |
+
|
119 |
+
eigs : list or dict
|
120 |
+
Eigenvalues of a matrix. The return format would be specified by
|
121 |
+
the key ``multiple``.
|
122 |
+
|
123 |
+
Raises
|
124 |
+
======
|
125 |
+
|
126 |
+
MatrixError
|
127 |
+
If not enough roots had got computed.
|
128 |
+
|
129 |
+
NonSquareMatrixError
|
130 |
+
If attempted to compute eigenvalues from a non-square matrix.
|
131 |
+
|
132 |
+
Examples
|
133 |
+
========
|
134 |
+
|
135 |
+
>>> from sympy import Matrix
|
136 |
+
>>> M = Matrix(3, 3, [0, 1, 1, 1, 0, 0, 1, 1, 1])
|
137 |
+
>>> M.eigenvals()
|
138 |
+
{-1: 1, 0: 1, 2: 1}
|
139 |
+
|
140 |
+
See Also
|
141 |
+
========
|
142 |
+
|
143 |
+
MatrixBase.charpoly
|
144 |
+
eigenvects
|
145 |
+
|
146 |
+
Notes
|
147 |
+
=====
|
148 |
+
|
149 |
+
Eigenvalues of a matrix $A$ can be computed by solving a matrix
|
150 |
+
equation $\det(A - \lambda I) = 0$
|
151 |
+
|
152 |
+
It's not always possible to return radical solutions for
|
153 |
+
eigenvalues for matrices larger than $4, 4$ shape due to
|
154 |
+
Abel-Ruffini theorem.
|
155 |
+
|
156 |
+
If there is no radical solution is found for the eigenvalue,
|
157 |
+
it may return eigenvalues in the form of
|
158 |
+
:class:`sympy.polys.rootoftools.ComplexRootOf`.
|
159 |
+
"""
|
160 |
+
if not M:
|
161 |
+
if multiple:
|
162 |
+
return []
|
163 |
+
return {}
|
164 |
+
|
165 |
+
if not M.is_square:
|
166 |
+
raise NonSquareMatrixError("{} must be a square matrix.".format(M))
|
167 |
+
|
168 |
+
if M._rep.domain not in (ZZ, QQ):
|
169 |
+
# Skip this check for ZZ/QQ because it can be slow
|
170 |
+
if all(x.is_number for x in M) and M.has(Float):
|
171 |
+
return _eigenvals_mpmath(M, multiple=multiple)
|
172 |
+
|
173 |
+
if rational:
|
174 |
+
from sympy.simplify import nsimplify
|
175 |
+
M = M.applyfunc(
|
176 |
+
lambda x: nsimplify(x, rational=True) if x.has(Float) else x)
|
177 |
+
|
178 |
+
if multiple:
|
179 |
+
return _eigenvals_list(
|
180 |
+
M, error_when_incomplete=error_when_incomplete, simplify=simplify,
|
181 |
+
**flags)
|
182 |
+
return _eigenvals_dict(
|
183 |
+
M, error_when_incomplete=error_when_incomplete, simplify=simplify,
|
184 |
+
**flags)
|
185 |
+
|
186 |
+
|
187 |
+
eigenvals_error_message = \
|
188 |
+
"It is not always possible to express the eigenvalues of a matrix " + \
|
189 |
+
"of size 5x5 or higher in radicals. " + \
|
190 |
+
"We have CRootOf, but domains other than the rationals are not " + \
|
191 |
+
"currently supported. " + \
|
192 |
+
"If there are no symbols in the matrix, " + \
|
193 |
+
"it should still be possible to compute numeric approximations " + \
|
194 |
+
"of the eigenvalues using " + \
|
195 |
+
"M.evalf().eigenvals() or M.charpoly().nroots()."
|
196 |
+
|
197 |
+
|
198 |
+
def _eigenvals_list(
|
199 |
+
M, error_when_incomplete=True, simplify=False, **flags):
|
200 |
+
iblocks = M.strongly_connected_components()
|
201 |
+
all_eigs = []
|
202 |
+
is_dom = M._rep.domain in (ZZ, QQ)
|
203 |
+
for b in iblocks:
|
204 |
+
|
205 |
+
# Fast path for a 1x1 block:
|
206 |
+
if is_dom and len(b) == 1:
|
207 |
+
index = b[0]
|
208 |
+
val = M[index, index]
|
209 |
+
all_eigs.append(val)
|
210 |
+
continue
|
211 |
+
|
212 |
+
block = M[b, b]
|
213 |
+
|
214 |
+
if isinstance(simplify, FunctionType):
|
215 |
+
charpoly = block.charpoly(simplify=simplify)
|
216 |
+
else:
|
217 |
+
charpoly = block.charpoly()
|
218 |
+
|
219 |
+
eigs = roots(charpoly, multiple=True, **flags)
|
220 |
+
|
221 |
+
if len(eigs) != block.rows:
|
222 |
+
try:
|
223 |
+
eigs = charpoly.all_roots(multiple=True)
|
224 |
+
except NotImplementedError:
|
225 |
+
if error_when_incomplete:
|
226 |
+
raise MatrixError(eigenvals_error_message)
|
227 |
+
else:
|
228 |
+
eigs = []
|
229 |
+
|
230 |
+
all_eigs += eigs
|
231 |
+
|
232 |
+
if not simplify:
|
233 |
+
return all_eigs
|
234 |
+
if not isinstance(simplify, FunctionType):
|
235 |
+
simplify = _simplify
|
236 |
+
return [simplify(value) for value in all_eigs]
|
237 |
+
|
238 |
+
|
239 |
+
def _eigenvals_dict(
|
240 |
+
M, error_when_incomplete=True, simplify=False, **flags):
|
241 |
+
iblocks = M.strongly_connected_components()
|
242 |
+
all_eigs = {}
|
243 |
+
is_dom = M._rep.domain in (ZZ, QQ)
|
244 |
+
for b in iblocks:
|
245 |
+
|
246 |
+
# Fast path for a 1x1 block:
|
247 |
+
if is_dom and len(b) == 1:
|
248 |
+
index = b[0]
|
249 |
+
val = M[index, index]
|
250 |
+
all_eigs[val] = all_eigs.get(val, 0) + 1
|
251 |
+
continue
|
252 |
+
|
253 |
+
block = M[b, b]
|
254 |
+
|
255 |
+
if isinstance(simplify, FunctionType):
|
256 |
+
charpoly = block.charpoly(simplify=simplify)
|
257 |
+
else:
|
258 |
+
charpoly = block.charpoly()
|
259 |
+
|
260 |
+
eigs = roots(charpoly, multiple=False, **flags)
|
261 |
+
|
262 |
+
if sum(eigs.values()) != block.rows:
|
263 |
+
try:
|
264 |
+
eigs = dict(charpoly.all_roots(multiple=False))
|
265 |
+
except NotImplementedError:
|
266 |
+
if error_when_incomplete:
|
267 |
+
raise MatrixError(eigenvals_error_message)
|
268 |
+
else:
|
269 |
+
eigs = {}
|
270 |
+
|
271 |
+
for k, v in eigs.items():
|
272 |
+
if k in all_eigs:
|
273 |
+
all_eigs[k] += v
|
274 |
+
else:
|
275 |
+
all_eigs[k] = v
|
276 |
+
|
277 |
+
if not simplify:
|
278 |
+
return all_eigs
|
279 |
+
if not isinstance(simplify, FunctionType):
|
280 |
+
simplify = _simplify
|
281 |
+
return {simplify(key): value for key, value in all_eigs.items()}
|
282 |
+
|
283 |
+
|
284 |
+
def _eigenspace(M, eigenval, iszerofunc=_iszero, simplify=False):
|
285 |
+
"""Get a basis for the eigenspace for a particular eigenvalue"""
|
286 |
+
m = M - M.eye(M.rows) * eigenval
|
287 |
+
ret = m.nullspace(iszerofunc=iszerofunc)
|
288 |
+
|
289 |
+
# The nullspace for a real eigenvalue should be non-trivial.
|
290 |
+
# If we didn't find an eigenvector, try once more a little harder
|
291 |
+
if len(ret) == 0 and simplify:
|
292 |
+
ret = m.nullspace(iszerofunc=iszerofunc, simplify=True)
|
293 |
+
if len(ret) == 0:
|
294 |
+
raise NotImplementedError(
|
295 |
+
"Can't evaluate eigenvector for eigenvalue {}".format(eigenval))
|
296 |
+
return ret
|
297 |
+
|
298 |
+
|
299 |
+
def _eigenvects_DOM(M, **kwargs):
|
300 |
+
DOM = DomainMatrix.from_Matrix(M, field=True, extension=True)
|
301 |
+
DOM = DOM.to_dense()
|
302 |
+
|
303 |
+
if DOM.domain != EX:
|
304 |
+
rational, algebraic = dom_eigenvects(DOM)
|
305 |
+
eigenvects = dom_eigenvects_to_sympy(
|
306 |
+
rational, algebraic, M.__class__, **kwargs)
|
307 |
+
eigenvects = sorted(eigenvects, key=lambda x: default_sort_key(x[0]))
|
308 |
+
|
309 |
+
return eigenvects
|
310 |
+
return None
|
311 |
+
|
312 |
+
|
313 |
+
def _eigenvects_sympy(M, iszerofunc, simplify=True, **flags):
|
314 |
+
eigenvals = M.eigenvals(rational=False, **flags)
|
315 |
+
|
316 |
+
# Make sure that we have all roots in radical form
|
317 |
+
for x in eigenvals:
|
318 |
+
if x.has(CRootOf):
|
319 |
+
raise MatrixError(
|
320 |
+
"Eigenvector computation is not implemented if the matrix have "
|
321 |
+
"eigenvalues in CRootOf form")
|
322 |
+
|
323 |
+
eigenvals = sorted(eigenvals.items(), key=default_sort_key)
|
324 |
+
ret = []
|
325 |
+
for val, mult in eigenvals:
|
326 |
+
vects = _eigenspace(M, val, iszerofunc=iszerofunc, simplify=simplify)
|
327 |
+
ret.append((val, mult, vects))
|
328 |
+
return ret
|
329 |
+
|
330 |
+
|
331 |
+
# This functions is a candidate for caching if it gets implemented for matrices.
|
332 |
+
def _eigenvects(M, error_when_incomplete=True, iszerofunc=_iszero, *, chop=False, **flags):
|
333 |
+
"""Compute eigenvectors of the matrix.
|
334 |
+
|
335 |
+
Parameters
|
336 |
+
==========
|
337 |
+
|
338 |
+
error_when_incomplete : bool, optional
|
339 |
+
Raise an error when not all eigenvalues are computed. This is
|
340 |
+
caused by ``roots`` not returning a full list of eigenvalues.
|
341 |
+
|
342 |
+
iszerofunc : function, optional
|
343 |
+
Specifies a zero testing function to be used in ``rref``.
|
344 |
+
|
345 |
+
Default value is ``_iszero``, which uses SymPy's naive and fast
|
346 |
+
default assumption handler.
|
347 |
+
|
348 |
+
It can also accept any user-specified zero testing function, if it
|
349 |
+
is formatted as a function which accepts a single symbolic argument
|
350 |
+
and returns ``True`` if it is tested as zero and ``False`` if it
|
351 |
+
is tested as non-zero, and ``None`` if it is undecidable.
|
352 |
+
|
353 |
+
simplify : bool or function, optional
|
354 |
+
If ``True``, ``as_content_primitive()`` will be used to tidy up
|
355 |
+
normalization artifacts.
|
356 |
+
|
357 |
+
It will also be used by the ``nullspace`` routine.
|
358 |
+
|
359 |
+
chop : bool or positive number, optional
|
360 |
+
If the matrix contains any Floats, they will be changed to Rationals
|
361 |
+
for computation purposes, but the answers will be returned after
|
362 |
+
being evaluated with evalf. The ``chop`` flag is passed to ``evalf``.
|
363 |
+
When ``chop=True`` a default precision will be used; a number will
|
364 |
+
be interpreted as the desired level of precision.
|
365 |
+
|
366 |
+
Returns
|
367 |
+
=======
|
368 |
+
|
369 |
+
ret : [(eigenval, multiplicity, eigenspace), ...]
|
370 |
+
A ragged list containing tuples of data obtained by ``eigenvals``
|
371 |
+
and ``nullspace``.
|
372 |
+
|
373 |
+
``eigenspace`` is a list containing the ``eigenvector`` for each
|
374 |
+
eigenvalue.
|
375 |
+
|
376 |
+
``eigenvector`` is a vector in the form of a ``Matrix``. e.g.
|
377 |
+
a vector of length 3 is returned as ``Matrix([a_1, a_2, a_3])``.
|
378 |
+
|
379 |
+
Raises
|
380 |
+
======
|
381 |
+
|
382 |
+
NotImplementedError
|
383 |
+
If failed to compute nullspace.
|
384 |
+
|
385 |
+
Examples
|
386 |
+
========
|
387 |
+
|
388 |
+
>>> from sympy import Matrix
|
389 |
+
>>> M = Matrix(3, 3, [0, 1, 1, 1, 0, 0, 1, 1, 1])
|
390 |
+
>>> M.eigenvects()
|
391 |
+
[(-1, 1, [Matrix([
|
392 |
+
[-1],
|
393 |
+
[ 1],
|
394 |
+
[ 0]])]), (0, 1, [Matrix([
|
395 |
+
[ 0],
|
396 |
+
[-1],
|
397 |
+
[ 1]])]), (2, 1, [Matrix([
|
398 |
+
[2/3],
|
399 |
+
[1/3],
|
400 |
+
[ 1]])])]
|
401 |
+
|
402 |
+
See Also
|
403 |
+
========
|
404 |
+
|
405 |
+
eigenvals
|
406 |
+
MatrixBase.nullspace
|
407 |
+
"""
|
408 |
+
simplify = flags.get('simplify', True)
|
409 |
+
primitive = flags.get('simplify', False)
|
410 |
+
flags.pop('simplify', None) # remove this if it's there
|
411 |
+
flags.pop('multiple', None) # remove this if it's there
|
412 |
+
|
413 |
+
if not isinstance(simplify, FunctionType):
|
414 |
+
simpfunc = _simplify if simplify else lambda x: x
|
415 |
+
|
416 |
+
has_floats = M.has(Float)
|
417 |
+
if has_floats:
|
418 |
+
if all(x.is_number for x in M):
|
419 |
+
return _eigenvects_mpmath(M)
|
420 |
+
from sympy.simplify import nsimplify
|
421 |
+
M = M.applyfunc(lambda x: nsimplify(x, rational=True))
|
422 |
+
|
423 |
+
ret = _eigenvects_DOM(M)
|
424 |
+
if ret is None:
|
425 |
+
ret = _eigenvects_sympy(M, iszerofunc, simplify=simplify, **flags)
|
426 |
+
|
427 |
+
if primitive:
|
428 |
+
# if the primitive flag is set, get rid of any common
|
429 |
+
# integer denominators
|
430 |
+
def denom_clean(l):
|
431 |
+
return [(v / gcd(list(v))).applyfunc(simpfunc) for v in l]
|
432 |
+
|
433 |
+
ret = [(val, mult, denom_clean(es)) for val, mult, es in ret]
|
434 |
+
|
435 |
+
if has_floats:
|
436 |
+
# if we had floats to start with, turn the eigenvectors to floats
|
437 |
+
ret = [(val.evalf(chop=chop), mult, [v.evalf(chop=chop) for v in es])
|
438 |
+
for val, mult, es in ret]
|
439 |
+
|
440 |
+
return ret
|
441 |
+
|
442 |
+
|
443 |
+
def _is_diagonalizable_with_eigen(M, reals_only=False):
|
444 |
+
"""See _is_diagonalizable. This function returns the bool along with the
|
445 |
+
eigenvectors to avoid calculating them again in functions like
|
446 |
+
``diagonalize``."""
|
447 |
+
|
448 |
+
if not M.is_square:
|
449 |
+
return False, []
|
450 |
+
|
451 |
+
eigenvecs = M.eigenvects(simplify=True)
|
452 |
+
|
453 |
+
for val, mult, basis in eigenvecs:
|
454 |
+
if reals_only and not val.is_real: # if we have a complex eigenvalue
|
455 |
+
return False, eigenvecs
|
456 |
+
|
457 |
+
if mult != len(basis): # if the geometric multiplicity doesn't equal the algebraic
|
458 |
+
return False, eigenvecs
|
459 |
+
|
460 |
+
return True, eigenvecs
|
461 |
+
|
462 |
+
def _is_diagonalizable(M, reals_only=False, **kwargs):
|
463 |
+
"""Returns ``True`` if a matrix is diagonalizable.
|
464 |
+
|
465 |
+
Parameters
|
466 |
+
==========
|
467 |
+
|
468 |
+
reals_only : bool, optional
|
469 |
+
If ``True``, it tests whether the matrix can be diagonalized
|
470 |
+
to contain only real numbers on the diagonal.
|
471 |
+
|
472 |
+
|
473 |
+
If ``False``, it tests whether the matrix can be diagonalized
|
474 |
+
at all, even with numbers that may not be real.
|
475 |
+
|
476 |
+
Examples
|
477 |
+
========
|
478 |
+
|
479 |
+
Example of a diagonalizable matrix:
|
480 |
+
|
481 |
+
>>> from sympy import Matrix
|
482 |
+
>>> M = Matrix([[1, 2, 0], [0, 3, 0], [2, -4, 2]])
|
483 |
+
>>> M.is_diagonalizable()
|
484 |
+
True
|
485 |
+
|
486 |
+
Example of a non-diagonalizable matrix:
|
487 |
+
|
488 |
+
>>> M = Matrix([[0, 1], [0, 0]])
|
489 |
+
>>> M.is_diagonalizable()
|
490 |
+
False
|
491 |
+
|
492 |
+
Example of a matrix that is diagonalized in terms of non-real entries:
|
493 |
+
|
494 |
+
>>> M = Matrix([[0, 1], [-1, 0]])
|
495 |
+
>>> M.is_diagonalizable(reals_only=False)
|
496 |
+
True
|
497 |
+
>>> M.is_diagonalizable(reals_only=True)
|
498 |
+
False
|
499 |
+
|
500 |
+
See Also
|
501 |
+
========
|
502 |
+
|
503 |
+
sympy.matrices.matrixbase.MatrixBase.is_diagonal
|
504 |
+
diagonalize
|
505 |
+
"""
|
506 |
+
if not M.is_square:
|
507 |
+
return False
|
508 |
+
|
509 |
+
if all(e.is_real for e in M) and M.is_symmetric():
|
510 |
+
return True
|
511 |
+
|
512 |
+
if all(e.is_complex for e in M) and M.is_hermitian:
|
513 |
+
return True
|
514 |
+
|
515 |
+
return _is_diagonalizable_with_eigen(M, reals_only=reals_only)[0]
|
516 |
+
|
517 |
+
|
518 |
+
#G&VL, Matrix Computations, Algo 5.4.2
|
519 |
+
def _householder_vector(x):
|
520 |
+
if not x.cols == 1:
|
521 |
+
raise ValueError("Input must be a column matrix")
|
522 |
+
v = x.copy()
|
523 |
+
v_plus = x.copy()
|
524 |
+
v_minus = x.copy()
|
525 |
+
q = x[0, 0] / abs(x[0, 0])
|
526 |
+
norm_x = x.norm()
|
527 |
+
v_plus[0, 0] = x[0, 0] + q * norm_x
|
528 |
+
v_minus[0, 0] = x[0, 0] - q * norm_x
|
529 |
+
if x[1:, 0].norm() == 0:
|
530 |
+
bet = 0
|
531 |
+
v[0, 0] = 1
|
532 |
+
else:
|
533 |
+
if v_plus.norm() <= v_minus.norm():
|
534 |
+
v = v_plus
|
535 |
+
else:
|
536 |
+
v = v_minus
|
537 |
+
v = v / v[0]
|
538 |
+
bet = 2 / (v.norm() ** 2)
|
539 |
+
return v, bet
|
540 |
+
|
541 |
+
|
542 |
+
def _bidiagonal_decmp_hholder(M):
|
543 |
+
m = M.rows
|
544 |
+
n = M.cols
|
545 |
+
A = M.as_mutable()
|
546 |
+
U, V = A.eye(m), A.eye(n)
|
547 |
+
for i in range(min(m, n)):
|
548 |
+
v, bet = _householder_vector(A[i:, i])
|
549 |
+
hh_mat = A.eye(m - i) - bet * v * v.H
|
550 |
+
A[i:, i:] = hh_mat * A[i:, i:]
|
551 |
+
temp = A.eye(m)
|
552 |
+
temp[i:, i:] = hh_mat
|
553 |
+
U = U * temp
|
554 |
+
if i + 1 <= n - 2:
|
555 |
+
v, bet = _householder_vector(A[i, i+1:].T)
|
556 |
+
hh_mat = A.eye(n - i - 1) - bet * v * v.H
|
557 |
+
A[i:, i+1:] = A[i:, i+1:] * hh_mat
|
558 |
+
temp = A.eye(n)
|
559 |
+
temp[i+1:, i+1:] = hh_mat
|
560 |
+
V = temp * V
|
561 |
+
return U, A, V
|
562 |
+
|
563 |
+
|
564 |
+
def _eval_bidiag_hholder(M):
|
565 |
+
m = M.rows
|
566 |
+
n = M.cols
|
567 |
+
A = M.as_mutable()
|
568 |
+
for i in range(min(m, n)):
|
569 |
+
v, bet = _householder_vector(A[i:, i])
|
570 |
+
hh_mat = A.eye(m-i) - bet * v * v.H
|
571 |
+
A[i:, i:] = hh_mat * A[i:, i:]
|
572 |
+
if i + 1 <= n - 2:
|
573 |
+
v, bet = _householder_vector(A[i, i+1:].T)
|
574 |
+
hh_mat = A.eye(n - i - 1) - bet * v * v.H
|
575 |
+
A[i:, i+1:] = A[i:, i+1:] * hh_mat
|
576 |
+
return A
|
577 |
+
|
578 |
+
|
579 |
+
def _bidiagonal_decomposition(M, upper=True):
|
580 |
+
"""
|
581 |
+
Returns $(U,B,V.H)$ for
|
582 |
+
|
583 |
+
$$A = UBV^{H}$$
|
584 |
+
|
585 |
+
where $A$ is the input matrix, and $B$ is its Bidiagonalized form
|
586 |
+
|
587 |
+
Note: Bidiagonal Computation can hang for symbolic matrices.
|
588 |
+
|
589 |
+
Parameters
|
590 |
+
==========
|
591 |
+
|
592 |
+
upper : bool. Whether to do upper bidiagnalization or lower.
|
593 |
+
True for upper and False for lower.
|
594 |
+
|
595 |
+
References
|
596 |
+
==========
|
597 |
+
|
598 |
+
.. [1] Algorithm 5.4.2, Matrix computations by Golub and Van Loan, 4th edition
|
599 |
+
.. [2] Complex Matrix Bidiagonalization, https://github.com/vslobody/Householder-Bidiagonalization
|
600 |
+
|
601 |
+
"""
|
602 |
+
|
603 |
+
if not isinstance(upper, bool):
|
604 |
+
raise ValueError("upper must be a boolean")
|
605 |
+
|
606 |
+
if upper:
|
607 |
+
return _bidiagonal_decmp_hholder(M)
|
608 |
+
|
609 |
+
X = _bidiagonal_decmp_hholder(M.H)
|
610 |
+
return X[2].H, X[1].H, X[0].H
|
611 |
+
|
612 |
+
|
613 |
+
def _bidiagonalize(M, upper=True):
|
614 |
+
"""
|
615 |
+
Returns $B$, the Bidiagonalized form of the input matrix.
|
616 |
+
|
617 |
+
Note: Bidiagonal Computation can hang for symbolic matrices.
|
618 |
+
|
619 |
+
Parameters
|
620 |
+
==========
|
621 |
+
|
622 |
+
upper : bool. Whether to do upper bidiagnalization or lower.
|
623 |
+
True for upper and False for lower.
|
624 |
+
|
625 |
+
References
|
626 |
+
==========
|
627 |
+
|
628 |
+
.. [1] Algorithm 5.4.2, Matrix computations by Golub and Van Loan, 4th edition
|
629 |
+
.. [2] Complex Matrix Bidiagonalization : https://github.com/vslobody/Householder-Bidiagonalization
|
630 |
+
|
631 |
+
"""
|
632 |
+
|
633 |
+
if not isinstance(upper, bool):
|
634 |
+
raise ValueError("upper must be a boolean")
|
635 |
+
|
636 |
+
if upper:
|
637 |
+
return _eval_bidiag_hholder(M)
|
638 |
+
return _eval_bidiag_hholder(M.H).H
|
639 |
+
|
640 |
+
|
641 |
+
def _diagonalize(M, reals_only=False, sort=False, normalize=False):
|
642 |
+
"""
|
643 |
+
Return (P, D), where D is diagonal and
|
644 |
+
|
645 |
+
D = P^-1 * M * P
|
646 |
+
|
647 |
+
where M is current matrix.
|
648 |
+
|
649 |
+
Parameters
|
650 |
+
==========
|
651 |
+
|
652 |
+
reals_only : bool. Whether to throw an error if complex numbers are need
|
653 |
+
to diagonalize. (Default: False)
|
654 |
+
|
655 |
+
sort : bool. Sort the eigenvalues along the diagonal. (Default: False)
|
656 |
+
|
657 |
+
normalize : bool. If True, normalize the columns of P. (Default: False)
|
658 |
+
|
659 |
+
Examples
|
660 |
+
========
|
661 |
+
|
662 |
+
>>> from sympy import Matrix
|
663 |
+
>>> M = Matrix(3, 3, [1, 2, 0, 0, 3, 0, 2, -4, 2])
|
664 |
+
>>> M
|
665 |
+
Matrix([
|
666 |
+
[1, 2, 0],
|
667 |
+
[0, 3, 0],
|
668 |
+
[2, -4, 2]])
|
669 |
+
>>> (P, D) = M.diagonalize()
|
670 |
+
>>> D
|
671 |
+
Matrix([
|
672 |
+
[1, 0, 0],
|
673 |
+
[0, 2, 0],
|
674 |
+
[0, 0, 3]])
|
675 |
+
>>> P
|
676 |
+
Matrix([
|
677 |
+
[-1, 0, -1],
|
678 |
+
[ 0, 0, -1],
|
679 |
+
[ 2, 1, 2]])
|
680 |
+
>>> P.inv() * M * P
|
681 |
+
Matrix([
|
682 |
+
[1, 0, 0],
|
683 |
+
[0, 2, 0],
|
684 |
+
[0, 0, 3]])
|
685 |
+
|
686 |
+
See Also
|
687 |
+
========
|
688 |
+
|
689 |
+
sympy.matrices.matrixbase.MatrixBase.is_diagonal
|
690 |
+
is_diagonalizable
|
691 |
+
"""
|
692 |
+
|
693 |
+
if not M.is_square:
|
694 |
+
raise NonSquareMatrixError()
|
695 |
+
|
696 |
+
is_diagonalizable, eigenvecs = _is_diagonalizable_with_eigen(M,
|
697 |
+
reals_only=reals_only)
|
698 |
+
|
699 |
+
if not is_diagonalizable:
|
700 |
+
raise MatrixError("Matrix is not diagonalizable")
|
701 |
+
|
702 |
+
if sort:
|
703 |
+
eigenvecs = sorted(eigenvecs, key=default_sort_key)
|
704 |
+
|
705 |
+
p_cols, diag = [], []
|
706 |
+
|
707 |
+
for val, mult, basis in eigenvecs:
|
708 |
+
diag += [val] * mult
|
709 |
+
p_cols += basis
|
710 |
+
|
711 |
+
if normalize:
|
712 |
+
p_cols = [v / v.norm() for v in p_cols]
|
713 |
+
|
714 |
+
return M.hstack(*p_cols), M.diag(*diag)
|
715 |
+
|
716 |
+
|
717 |
+
def _fuzzy_positive_definite(M):
|
718 |
+
positive_diagonals = M._has_positive_diagonals()
|
719 |
+
if positive_diagonals is False:
|
720 |
+
return False
|
721 |
+
|
722 |
+
if positive_diagonals and M.is_strongly_diagonally_dominant:
|
723 |
+
return True
|
724 |
+
|
725 |
+
return None
|
726 |
+
|
727 |
+
|
728 |
+
def _fuzzy_positive_semidefinite(M):
|
729 |
+
nonnegative_diagonals = M._has_nonnegative_diagonals()
|
730 |
+
if nonnegative_diagonals is False:
|
731 |
+
return False
|
732 |
+
|
733 |
+
if nonnegative_diagonals and M.is_weakly_diagonally_dominant:
|
734 |
+
return True
|
735 |
+
|
736 |
+
return None
|
737 |
+
|
738 |
+
|
739 |
+
def _is_positive_definite(M):
|
740 |
+
if not M.is_hermitian:
|
741 |
+
if not M.is_square:
|
742 |
+
return False
|
743 |
+
M = M + M.H
|
744 |
+
|
745 |
+
fuzzy = _fuzzy_positive_definite(M)
|
746 |
+
if fuzzy is not None:
|
747 |
+
return fuzzy
|
748 |
+
|
749 |
+
return _is_positive_definite_GE(M)
|
750 |
+
|
751 |
+
|
752 |
+
def _is_positive_semidefinite(M):
|
753 |
+
if not M.is_hermitian:
|
754 |
+
if not M.is_square:
|
755 |
+
return False
|
756 |
+
M = M + M.H
|
757 |
+
|
758 |
+
fuzzy = _fuzzy_positive_semidefinite(M)
|
759 |
+
if fuzzy is not None:
|
760 |
+
return fuzzy
|
761 |
+
|
762 |
+
return _is_positive_semidefinite_cholesky(M)
|
763 |
+
|
764 |
+
|
765 |
+
def _is_negative_definite(M):
|
766 |
+
return _is_positive_definite(-M)
|
767 |
+
|
768 |
+
|
769 |
+
def _is_negative_semidefinite(M):
|
770 |
+
return _is_positive_semidefinite(-M)
|
771 |
+
|
772 |
+
|
773 |
+
def _is_indefinite(M):
|
774 |
+
if M.is_hermitian:
|
775 |
+
eigen = M.eigenvals()
|
776 |
+
args1 = [x.is_positive for x in eigen.keys()]
|
777 |
+
any_positive = fuzzy_or(args1)
|
778 |
+
args2 = [x.is_negative for x in eigen.keys()]
|
779 |
+
any_negative = fuzzy_or(args2)
|
780 |
+
|
781 |
+
return fuzzy_and([any_positive, any_negative])
|
782 |
+
|
783 |
+
elif M.is_square:
|
784 |
+
return (M + M.H).is_indefinite
|
785 |
+
|
786 |
+
return False
|
787 |
+
|
788 |
+
|
789 |
+
def _is_positive_definite_GE(M):
|
790 |
+
"""A division-free gaussian elimination method for testing
|
791 |
+
positive-definiteness."""
|
792 |
+
M = M.as_mutable()
|
793 |
+
size = M.rows
|
794 |
+
|
795 |
+
for i in range(size):
|
796 |
+
is_positive = M[i, i].is_positive
|
797 |
+
if is_positive is not True:
|
798 |
+
return is_positive
|
799 |
+
for j in range(i+1, size):
|
800 |
+
M[j, i+1:] = M[i, i] * M[j, i+1:] - M[j, i] * M[i, i+1:]
|
801 |
+
return True
|
802 |
+
|
803 |
+
|
804 |
+
def _is_positive_semidefinite_cholesky(M):
|
805 |
+
"""Uses Cholesky factorization with complete pivoting
|
806 |
+
|
807 |
+
References
|
808 |
+
==========
|
809 |
+
|
810 |
+
.. [1] http://eprints.ma.man.ac.uk/1199/1/covered/MIMS_ep2008_116.pdf
|
811 |
+
|
812 |
+
.. [2] https://www.value-at-risk.net/cholesky-factorization/
|
813 |
+
"""
|
814 |
+
M = M.as_mutable()
|
815 |
+
for k in range(M.rows):
|
816 |
+
diags = [M[i, i] for i in range(k, M.rows)]
|
817 |
+
pivot, pivot_val, nonzero, _ = _find_reasonable_pivot(diags)
|
818 |
+
|
819 |
+
if nonzero:
|
820 |
+
return None
|
821 |
+
|
822 |
+
if pivot is None:
|
823 |
+
for i in range(k+1, M.rows):
|
824 |
+
for j in range(k, M.cols):
|
825 |
+
iszero = M[i, j].is_zero
|
826 |
+
if iszero is None:
|
827 |
+
return None
|
828 |
+
elif iszero is False:
|
829 |
+
return False
|
830 |
+
return True
|
831 |
+
|
832 |
+
if M[k, k].is_negative or pivot_val.is_negative:
|
833 |
+
return False
|
834 |
+
elif not (M[k, k].is_nonnegative and pivot_val.is_nonnegative):
|
835 |
+
return None
|
836 |
+
|
837 |
+
if pivot > 0:
|
838 |
+
M.col_swap(k, k+pivot)
|
839 |
+
M.row_swap(k, k+pivot)
|
840 |
+
|
841 |
+
M[k, k] = sqrt(M[k, k])
|
842 |
+
M[k, k+1:] /= M[k, k]
|
843 |
+
M[k+1:, k+1:] -= M[k, k+1:].H * M[k, k+1:]
|
844 |
+
|
845 |
+
return M[-1, -1].is_nonnegative
|
846 |
+
|
847 |
+
|
848 |
+
_doc_positive_definite = \
|
849 |
+
r"""Finds out the definiteness of a matrix.
|
850 |
+
|
851 |
+
Explanation
|
852 |
+
===========
|
853 |
+
|
854 |
+
A square real matrix $A$ is:
|
855 |
+
|
856 |
+
- A positive definite matrix if $x^T A x > 0$
|
857 |
+
for all non-zero real vectors $x$.
|
858 |
+
- A positive semidefinite matrix if $x^T A x \geq 0$
|
859 |
+
for all non-zero real vectors $x$.
|
860 |
+
- A negative definite matrix if $x^T A x < 0$
|
861 |
+
for all non-zero real vectors $x$.
|
862 |
+
- A negative semidefinite matrix if $x^T A x \leq 0$
|
863 |
+
for all non-zero real vectors $x$.
|
864 |
+
- An indefinite matrix if there exists non-zero real vectors
|
865 |
+
$x, y$ with $x^T A x > 0 > y^T A y$.
|
866 |
+
|
867 |
+
A square complex matrix $A$ is:
|
868 |
+
|
869 |
+
- A positive definite matrix if $\text{re}(x^H A x) > 0$
|
870 |
+
for all non-zero complex vectors $x$.
|
871 |
+
- A positive semidefinite matrix if $\text{re}(x^H A x) \geq 0$
|
872 |
+
for all non-zero complex vectors $x$.
|
873 |
+
- A negative definite matrix if $\text{re}(x^H A x) < 0$
|
874 |
+
for all non-zero complex vectors $x$.
|
875 |
+
- A negative semidefinite matrix if $\text{re}(x^H A x) \leq 0$
|
876 |
+
for all non-zero complex vectors $x$.
|
877 |
+
- An indefinite matrix if there exists non-zero complex vectors
|
878 |
+
$x, y$ with $\text{re}(x^H A x) > 0 > \text{re}(y^H A y)$.
|
879 |
+
|
880 |
+
A matrix need not be symmetric or hermitian to be positive definite.
|
881 |
+
|
882 |
+
- A real non-symmetric matrix is positive definite if and only if
|
883 |
+
$\frac{A + A^T}{2}$ is positive definite.
|
884 |
+
- A complex non-hermitian matrix is positive definite if and only if
|
885 |
+
$\frac{A + A^H}{2}$ is positive definite.
|
886 |
+
|
887 |
+
And this extension can apply for all the definitions above.
|
888 |
+
|
889 |
+
However, for complex cases, you can restrict the definition of
|
890 |
+
$\text{re}(x^H A x) > 0$ to $x^H A x > 0$ and require the matrix
|
891 |
+
to be hermitian.
|
892 |
+
But we do not present this restriction for computation because you
|
893 |
+
can check ``M.is_hermitian`` independently with this and use
|
894 |
+
the same procedure.
|
895 |
+
|
896 |
+
Examples
|
897 |
+
========
|
898 |
+
|
899 |
+
An example of symmetric positive definite matrix:
|
900 |
+
|
901 |
+
.. plot::
|
902 |
+
:context: reset
|
903 |
+
:format: doctest
|
904 |
+
:include-source: True
|
905 |
+
|
906 |
+
>>> from sympy import Matrix, symbols
|
907 |
+
>>> from sympy.plotting import plot3d
|
908 |
+
>>> a, b = symbols('a b')
|
909 |
+
>>> x = Matrix([a, b])
|
910 |
+
|
911 |
+
>>> A = Matrix([[1, 0], [0, 1]])
|
912 |
+
>>> A.is_positive_definite
|
913 |
+
True
|
914 |
+
>>> A.is_positive_semidefinite
|
915 |
+
True
|
916 |
+
|
917 |
+
>>> p = plot3d((x.T*A*x)[0, 0], (a, -1, 1), (b, -1, 1))
|
918 |
+
|
919 |
+
An example of symmetric positive semidefinite matrix:
|
920 |
+
|
921 |
+
.. plot::
|
922 |
+
:context: close-figs
|
923 |
+
:format: doctest
|
924 |
+
:include-source: True
|
925 |
+
|
926 |
+
>>> A = Matrix([[1, -1], [-1, 1]])
|
927 |
+
>>> A.is_positive_definite
|
928 |
+
False
|
929 |
+
>>> A.is_positive_semidefinite
|
930 |
+
True
|
931 |
+
|
932 |
+
>>> p = plot3d((x.T*A*x)[0, 0], (a, -1, 1), (b, -1, 1))
|
933 |
+
|
934 |
+
An example of symmetric negative definite matrix:
|
935 |
+
|
936 |
+
.. plot::
|
937 |
+
:context: close-figs
|
938 |
+
:format: doctest
|
939 |
+
:include-source: True
|
940 |
+
|
941 |
+
>>> A = Matrix([[-1, 0], [0, -1]])
|
942 |
+
>>> A.is_negative_definite
|
943 |
+
True
|
944 |
+
>>> A.is_negative_semidefinite
|
945 |
+
True
|
946 |
+
>>> A.is_indefinite
|
947 |
+
False
|
948 |
+
|
949 |
+
>>> p = plot3d((x.T*A*x)[0, 0], (a, -1, 1), (b, -1, 1))
|
950 |
+
|
951 |
+
An example of symmetric indefinite matrix:
|
952 |
+
|
953 |
+
.. plot::
|
954 |
+
:context: close-figs
|
955 |
+
:format: doctest
|
956 |
+
:include-source: True
|
957 |
+
|
958 |
+
>>> A = Matrix([[1, 2], [2, -1]])
|
959 |
+
>>> A.is_indefinite
|
960 |
+
True
|
961 |
+
|
962 |
+
>>> p = plot3d((x.T*A*x)[0, 0], (a, -1, 1), (b, -1, 1))
|
963 |
+
|
964 |
+
An example of non-symmetric positive definite matrix.
|
965 |
+
|
966 |
+
.. plot::
|
967 |
+
:context: close-figs
|
968 |
+
:format: doctest
|
969 |
+
:include-source: True
|
970 |
+
|
971 |
+
>>> A = Matrix([[1, 2], [-2, 1]])
|
972 |
+
>>> A.is_positive_definite
|
973 |
+
True
|
974 |
+
>>> A.is_positive_semidefinite
|
975 |
+
True
|
976 |
+
|
977 |
+
>>> p = plot3d((x.T*A*x)[0, 0], (a, -1, 1), (b, -1, 1))
|
978 |
+
|
979 |
+
Notes
|
980 |
+
=====
|
981 |
+
|
982 |
+
Although some people trivialize the definition of positive definite
|
983 |
+
matrices only for symmetric or hermitian matrices, this restriction
|
984 |
+
is not correct because it does not classify all instances of
|
985 |
+
positive definite matrices from the definition $x^T A x > 0$ or
|
986 |
+
$\text{re}(x^H A x) > 0$.
|
987 |
+
|
988 |
+
For instance, ``Matrix([[1, 2], [-2, 1]])`` presented in
|
989 |
+
the example above is an example of real positive definite matrix
|
990 |
+
that is not symmetric.
|
991 |
+
|
992 |
+
However, since the following formula holds true;
|
993 |
+
|
994 |
+
.. math::
|
995 |
+
\text{re}(x^H A x) > 0 \iff
|
996 |
+
\text{re}(x^H \frac{A + A^H}{2} x) > 0
|
997 |
+
|
998 |
+
We can classify all positive definite matrices that may or may not
|
999 |
+
be symmetric or hermitian by transforming the matrix to
|
1000 |
+
$\frac{A + A^T}{2}$ or $\frac{A + A^H}{2}$
|
1001 |
+
(which is guaranteed to be always real symmetric or complex
|
1002 |
+
hermitian) and we can defer most of the studies to symmetric or
|
1003 |
+
hermitian positive definite matrices.
|
1004 |
+
|
1005 |
+
But it is a different problem for the existence of Cholesky
|
1006 |
+
decomposition. Because even though a non symmetric or a non
|
1007 |
+
hermitian matrix can be positive definite, Cholesky or LDL
|
1008 |
+
decomposition does not exist because the decompositions require the
|
1009 |
+
matrix to be symmetric or hermitian.
|
1010 |
+
|
1011 |
+
References
|
1012 |
+
==========
|
1013 |
+
|
1014 |
+
.. [1] https://en.wikipedia.org/wiki/Definiteness_of_a_matrix#Eigenvalues
|
1015 |
+
|
1016 |
+
.. [2] https://mathworld.wolfram.com/PositiveDefiniteMatrix.html
|
1017 |
+
|
1018 |
+
.. [3] Johnson, C. R. "Positive Definite Matrices." Amer.
|
1019 |
+
Math. Monthly 77, 259-264 1970.
|
1020 |
+
"""
|
1021 |
+
|
1022 |
+
_is_positive_definite.__doc__ = _doc_positive_definite
|
1023 |
+
_is_positive_semidefinite.__doc__ = _doc_positive_definite
|
1024 |
+
_is_negative_definite.__doc__ = _doc_positive_definite
|
1025 |
+
_is_negative_semidefinite.__doc__ = _doc_positive_definite
|
1026 |
+
_is_indefinite.__doc__ = _doc_positive_definite
|
1027 |
+
|
1028 |
+
|
1029 |
+
def _jordan_form(M, calc_transform=True, *, chop=False):
|
1030 |
+
"""Return $(P, J)$ where $J$ is a Jordan block
|
1031 |
+
matrix and $P$ is a matrix such that $M = P J P^{-1}$
|
1032 |
+
|
1033 |
+
Parameters
|
1034 |
+
==========
|
1035 |
+
|
1036 |
+
calc_transform : bool
|
1037 |
+
If ``False``, then only $J$ is returned.
|
1038 |
+
|
1039 |
+
chop : bool
|
1040 |
+
All matrices are converted to exact types when computing
|
1041 |
+
eigenvalues and eigenvectors. As a result, there may be
|
1042 |
+
approximation errors. If ``chop==True``, these errors
|
1043 |
+
will be truncated.
|
1044 |
+
|
1045 |
+
Examples
|
1046 |
+
========
|
1047 |
+
|
1048 |
+
>>> from sympy import Matrix
|
1049 |
+
>>> M = Matrix([[ 6, 5, -2, -3], [-3, -1, 3, 3], [ 2, 1, -2, -3], [-1, 1, 5, 5]])
|
1050 |
+
>>> P, J = M.jordan_form()
|
1051 |
+
>>> J
|
1052 |
+
Matrix([
|
1053 |
+
[2, 1, 0, 0],
|
1054 |
+
[0, 2, 0, 0],
|
1055 |
+
[0, 0, 2, 1],
|
1056 |
+
[0, 0, 0, 2]])
|
1057 |
+
|
1058 |
+
See Also
|
1059 |
+
========
|
1060 |
+
|
1061 |
+
jordan_block
|
1062 |
+
"""
|
1063 |
+
|
1064 |
+
if not M.is_square:
|
1065 |
+
raise NonSquareMatrixError("Only square matrices have Jordan forms")
|
1066 |
+
|
1067 |
+
mat = M
|
1068 |
+
has_floats = M.has(Float)
|
1069 |
+
|
1070 |
+
if has_floats:
|
1071 |
+
try:
|
1072 |
+
max_prec = max(term._prec for term in M.values() if isinstance(term, Float))
|
1073 |
+
except ValueError:
|
1074 |
+
# if no term in the matrix is explicitly a Float calling max()
|
1075 |
+
# will throw a error so setting max_prec to default value of 53
|
1076 |
+
max_prec = 53
|
1077 |
+
|
1078 |
+
# setting minimum max_dps to 15 to prevent loss of precision in
|
1079 |
+
# matrix containing non evaluated expressions
|
1080 |
+
max_dps = max(prec_to_dps(max_prec), 15)
|
1081 |
+
|
1082 |
+
def restore_floats(*args):
|
1083 |
+
"""If ``has_floats`` is `True`, cast all ``args`` as
|
1084 |
+
matrices of floats."""
|
1085 |
+
|
1086 |
+
if has_floats:
|
1087 |
+
args = [m.evalf(n=max_dps, chop=chop) for m in args]
|
1088 |
+
if len(args) == 1:
|
1089 |
+
return args[0]
|
1090 |
+
|
1091 |
+
return args
|
1092 |
+
|
1093 |
+
# cache calculations for some speedup
|
1094 |
+
mat_cache = {}
|
1095 |
+
|
1096 |
+
def eig_mat(val, pow):
|
1097 |
+
"""Cache computations of ``(M - val*I)**pow`` for quick
|
1098 |
+
retrieval"""
|
1099 |
+
|
1100 |
+
if (val, pow) in mat_cache:
|
1101 |
+
return mat_cache[(val, pow)]
|
1102 |
+
|
1103 |
+
if (val, pow - 1) in mat_cache:
|
1104 |
+
mat_cache[(val, pow)] = mat_cache[(val, pow - 1)].multiply(
|
1105 |
+
mat_cache[(val, 1)], dotprodsimp=None)
|
1106 |
+
else:
|
1107 |
+
mat_cache[(val, pow)] = (mat - val*M.eye(M.rows)).pow(pow)
|
1108 |
+
|
1109 |
+
return mat_cache[(val, pow)]
|
1110 |
+
|
1111 |
+
# helper functions
|
1112 |
+
def nullity_chain(val, algebraic_multiplicity):
|
1113 |
+
"""Calculate the sequence [0, nullity(E), nullity(E**2), ...]
|
1114 |
+
until it is constant where ``E = M - val*I``"""
|
1115 |
+
|
1116 |
+
# mat.rank() is faster than computing the null space,
|
1117 |
+
# so use the rank-nullity theorem
|
1118 |
+
cols = M.cols
|
1119 |
+
ret = [0]
|
1120 |
+
nullity = cols - eig_mat(val, 1).rank()
|
1121 |
+
i = 2
|
1122 |
+
|
1123 |
+
while nullity != ret[-1]:
|
1124 |
+
ret.append(nullity)
|
1125 |
+
|
1126 |
+
if nullity == algebraic_multiplicity:
|
1127 |
+
break
|
1128 |
+
|
1129 |
+
nullity = cols - eig_mat(val, i).rank()
|
1130 |
+
i += 1
|
1131 |
+
|
1132 |
+
# Due to issues like #7146 and #15872, SymPy sometimes
|
1133 |
+
# gives the wrong rank. In this case, raise an error
|
1134 |
+
# instead of returning an incorrect matrix
|
1135 |
+
if nullity < ret[-1] or nullity > algebraic_multiplicity:
|
1136 |
+
raise MatrixError(
|
1137 |
+
"SymPy had encountered an inconsistent "
|
1138 |
+
"result while computing Jordan block: "
|
1139 |
+
"{}".format(M))
|
1140 |
+
|
1141 |
+
return ret
|
1142 |
+
|
1143 |
+
def blocks_from_nullity_chain(d):
|
1144 |
+
"""Return a list of the size of each Jordan block.
|
1145 |
+
If d_n is the nullity of E**n, then the number
|
1146 |
+
of Jordan blocks of size n is
|
1147 |
+
|
1148 |
+
2*d_n - d_(n-1) - d_(n+1)"""
|
1149 |
+
|
1150 |
+
# d[0] is always the number of columns, so skip past it
|
1151 |
+
mid = [2*d[n] - d[n - 1] - d[n + 1] for n in range(1, len(d) - 1)]
|
1152 |
+
# d is assumed to plateau with "d[ len(d) ] == d[-1]", so
|
1153 |
+
# 2*d_n - d_(n-1) - d_(n+1) == d_n - d_(n-1)
|
1154 |
+
end = [d[-1] - d[-2]] if len(d) > 1 else [d[0]]
|
1155 |
+
|
1156 |
+
return mid + end
|
1157 |
+
|
1158 |
+
def pick_vec(small_basis, big_basis):
|
1159 |
+
"""Picks a vector from big_basis that isn't in
|
1160 |
+
the subspace spanned by small_basis"""
|
1161 |
+
|
1162 |
+
if len(small_basis) == 0:
|
1163 |
+
return big_basis[0]
|
1164 |
+
|
1165 |
+
for v in big_basis:
|
1166 |
+
_, pivots = M.hstack(*(small_basis + [v])).echelon_form(
|
1167 |
+
with_pivots=True)
|
1168 |
+
|
1169 |
+
if pivots[-1] == len(small_basis):
|
1170 |
+
return v
|
1171 |
+
|
1172 |
+
# roots doesn't like Floats, so replace them with Rationals
|
1173 |
+
if has_floats:
|
1174 |
+
from sympy.simplify import nsimplify
|
1175 |
+
mat = mat.applyfunc(lambda x: nsimplify(x, rational=True))
|
1176 |
+
|
1177 |
+
# first calculate the jordan block structure
|
1178 |
+
eigs = mat.eigenvals()
|
1179 |
+
|
1180 |
+
# Make sure that we have all roots in radical form
|
1181 |
+
for x in eigs:
|
1182 |
+
if x.has(CRootOf):
|
1183 |
+
raise MatrixError(
|
1184 |
+
"Jordan normal form is not implemented if the matrix have "
|
1185 |
+
"eigenvalues in CRootOf form")
|
1186 |
+
|
1187 |
+
# most matrices have distinct eigenvalues
|
1188 |
+
# and so are diagonalizable. In this case, don't
|
1189 |
+
# do extra work!
|
1190 |
+
if len(eigs.keys()) == mat.cols:
|
1191 |
+
blocks = sorted(eigs.keys(), key=default_sort_key)
|
1192 |
+
jordan_mat = mat.diag(*blocks)
|
1193 |
+
|
1194 |
+
if not calc_transform:
|
1195 |
+
return restore_floats(jordan_mat)
|
1196 |
+
|
1197 |
+
jordan_basis = [eig_mat(eig, 1).nullspace()[0]
|
1198 |
+
for eig in blocks]
|
1199 |
+
basis_mat = mat.hstack(*jordan_basis)
|
1200 |
+
|
1201 |
+
return restore_floats(basis_mat, jordan_mat)
|
1202 |
+
|
1203 |
+
block_structure = []
|
1204 |
+
|
1205 |
+
for eig in sorted(eigs.keys(), key=default_sort_key):
|
1206 |
+
algebraic_multiplicity = eigs[eig]
|
1207 |
+
chain = nullity_chain(eig, algebraic_multiplicity)
|
1208 |
+
block_sizes = blocks_from_nullity_chain(chain)
|
1209 |
+
|
1210 |
+
# if block_sizes = = [a, b, c, ...], then the number of
|
1211 |
+
# Jordan blocks of size 1 is a, of size 2 is b, etc.
|
1212 |
+
# create an array that has (eig, block_size) with one
|
1213 |
+
# entry for each block
|
1214 |
+
size_nums = [(i+1, num) for i, num in enumerate(block_sizes)]
|
1215 |
+
|
1216 |
+
# we expect larger Jordan blocks to come earlier
|
1217 |
+
size_nums.reverse()
|
1218 |
+
|
1219 |
+
block_structure.extend(
|
1220 |
+
[(eig, size) for size, num in size_nums for _ in range(num)])
|
1221 |
+
|
1222 |
+
jordan_form_size = sum(size for eig, size in block_structure)
|
1223 |
+
|
1224 |
+
if jordan_form_size != M.rows:
|
1225 |
+
raise MatrixError(
|
1226 |
+
"SymPy had encountered an inconsistent result while "
|
1227 |
+
"computing Jordan block. : {}".format(M))
|
1228 |
+
|
1229 |
+
blocks = (mat.jordan_block(size=size, eigenvalue=eig) for eig, size in block_structure)
|
1230 |
+
jordan_mat = mat.diag(*blocks)
|
1231 |
+
|
1232 |
+
if not calc_transform:
|
1233 |
+
return restore_floats(jordan_mat)
|
1234 |
+
|
1235 |
+
# For each generalized eigenspace, calculate a basis.
|
1236 |
+
# We start by looking for a vector in null( (A - eig*I)**n )
|
1237 |
+
# which isn't in null( (A - eig*I)**(n-1) ) where n is
|
1238 |
+
# the size of the Jordan block
|
1239 |
+
#
|
1240 |
+
# Ideally we'd just loop through block_structure and
|
1241 |
+
# compute each generalized eigenspace. However, this
|
1242 |
+
# causes a lot of unneeded computation. Instead, we
|
1243 |
+
# go through the eigenvalues separately, since we know
|
1244 |
+
# their generalized eigenspaces must have bases that
|
1245 |
+
# are linearly independent.
|
1246 |
+
jordan_basis = []
|
1247 |
+
|
1248 |
+
for eig in sorted(eigs.keys(), key=default_sort_key):
|
1249 |
+
eig_basis = []
|
1250 |
+
|
1251 |
+
for block_eig, size in block_structure:
|
1252 |
+
if block_eig != eig:
|
1253 |
+
continue
|
1254 |
+
|
1255 |
+
null_big = (eig_mat(eig, size)).nullspace()
|
1256 |
+
null_small = (eig_mat(eig, size - 1)).nullspace()
|
1257 |
+
|
1258 |
+
# we want to pick something that is in the big basis
|
1259 |
+
# and not the small, but also something that is independent
|
1260 |
+
# of any other generalized eigenvectors from a different
|
1261 |
+
# generalized eigenspace sharing the same eigenvalue.
|
1262 |
+
vec = pick_vec(null_small + eig_basis, null_big)
|
1263 |
+
new_vecs = [eig_mat(eig, i).multiply(vec, dotprodsimp=None)
|
1264 |
+
for i in range(size)]
|
1265 |
+
|
1266 |
+
eig_basis.extend(new_vecs)
|
1267 |
+
jordan_basis.extend(reversed(new_vecs))
|
1268 |
+
|
1269 |
+
basis_mat = mat.hstack(*jordan_basis)
|
1270 |
+
|
1271 |
+
return restore_floats(basis_mat, jordan_mat)
|
1272 |
+
|
1273 |
+
|
1274 |
+
def _left_eigenvects(M, **flags):
|
1275 |
+
"""Returns left eigenvectors and eigenvalues.
|
1276 |
+
|
1277 |
+
This function returns the list of triples (eigenval, multiplicity,
|
1278 |
+
basis) for the left eigenvectors. Options are the same as for
|
1279 |
+
eigenvects(), i.e. the ``**flags`` arguments gets passed directly to
|
1280 |
+
eigenvects().
|
1281 |
+
|
1282 |
+
Examples
|
1283 |
+
========
|
1284 |
+
|
1285 |
+
>>> from sympy import Matrix
|
1286 |
+
>>> M = Matrix([[0, 1, 1], [1, 0, 0], [1, 1, 1]])
|
1287 |
+
>>> M.eigenvects()
|
1288 |
+
[(-1, 1, [Matrix([
|
1289 |
+
[-1],
|
1290 |
+
[ 1],
|
1291 |
+
[ 0]])]), (0, 1, [Matrix([
|
1292 |
+
[ 0],
|
1293 |
+
[-1],
|
1294 |
+
[ 1]])]), (2, 1, [Matrix([
|
1295 |
+
[2/3],
|
1296 |
+
[1/3],
|
1297 |
+
[ 1]])])]
|
1298 |
+
>>> M.left_eigenvects()
|
1299 |
+
[(-1, 1, [Matrix([[-2, 1, 1]])]), (0, 1, [Matrix([[-1, -1, 1]])]), (2,
|
1300 |
+
1, [Matrix([[1, 1, 1]])])]
|
1301 |
+
|
1302 |
+
"""
|
1303 |
+
|
1304 |
+
eigs = M.transpose().eigenvects(**flags)
|
1305 |
+
|
1306 |
+
return [(val, mult, [l.transpose() for l in basis]) for val, mult, basis in eigs]
|
1307 |
+
|
1308 |
+
|
1309 |
+
def _singular_values(M):
|
1310 |
+
"""Compute the singular values of a Matrix
|
1311 |
+
|
1312 |
+
Examples
|
1313 |
+
========
|
1314 |
+
|
1315 |
+
>>> from sympy import Matrix, Symbol
|
1316 |
+
>>> x = Symbol('x', real=True)
|
1317 |
+
>>> M = Matrix([[0, 1, 0], [0, x, 0], [-1, 0, 0]])
|
1318 |
+
>>> M.singular_values()
|
1319 |
+
[sqrt(x**2 + 1), 1, 0]
|
1320 |
+
|
1321 |
+
See Also
|
1322 |
+
========
|
1323 |
+
|
1324 |
+
condition_number
|
1325 |
+
"""
|
1326 |
+
|
1327 |
+
if M.rows >= M.cols:
|
1328 |
+
valmultpairs = M.H.multiply(M).eigenvals()
|
1329 |
+
else:
|
1330 |
+
valmultpairs = M.multiply(M.H).eigenvals()
|
1331 |
+
|
1332 |
+
# Expands result from eigenvals into a simple list
|
1333 |
+
vals = []
|
1334 |
+
|
1335 |
+
for k, v in valmultpairs.items():
|
1336 |
+
vals += [sqrt(k)] * v # dangerous! same k in several spots!
|
1337 |
+
|
1338 |
+
# Pad with zeros if singular values are computed in reverse way,
|
1339 |
+
# to give consistent format.
|
1340 |
+
if len(vals) < M.cols:
|
1341 |
+
vals += [M.zero] * (M.cols - len(vals))
|
1342 |
+
|
1343 |
+
# sort them in descending order
|
1344 |
+
vals.sort(reverse=True, key=default_sort_key)
|
1345 |
+
|
1346 |
+
return vals
|
.venv/lib/python3.11/site-packages/sympy/matrices/expressions/__init__.py
ADDED
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
""" A module which handles Matrix Expressions """
|
2 |
+
|
3 |
+
from .slice import MatrixSlice
|
4 |
+
from .blockmatrix import BlockMatrix, BlockDiagMatrix, block_collapse, blockcut
|
5 |
+
from .companion import CompanionMatrix
|
6 |
+
from .funcmatrix import FunctionMatrix
|
7 |
+
from .inverse import Inverse
|
8 |
+
from .matadd import MatAdd
|
9 |
+
from .matexpr import MatrixExpr, MatrixSymbol, matrix_symbols
|
10 |
+
from .matmul import MatMul
|
11 |
+
from .matpow import MatPow
|
12 |
+
from .trace import Trace, trace
|
13 |
+
from .determinant import Determinant, det, Permanent, per
|
14 |
+
from .transpose import Transpose
|
15 |
+
from .adjoint import Adjoint
|
16 |
+
from .hadamard import hadamard_product, HadamardProduct, hadamard_power, HadamardPower
|
17 |
+
from .diagonal import DiagonalMatrix, DiagonalOf, DiagMatrix, diagonalize_vector
|
18 |
+
from .dotproduct import DotProduct
|
19 |
+
from .kronecker import kronecker_product, KroneckerProduct, combine_kronecker
|
20 |
+
from .permutation import PermutationMatrix, MatrixPermute
|
21 |
+
from .sets import MatrixSet
|
22 |
+
from .special import ZeroMatrix, Identity, OneMatrix
|
23 |
+
|
24 |
+
__all__ = [
|
25 |
+
'MatrixSlice',
|
26 |
+
|
27 |
+
'BlockMatrix', 'BlockDiagMatrix', 'block_collapse', 'blockcut',
|
28 |
+
'FunctionMatrix',
|
29 |
+
|
30 |
+
'CompanionMatrix',
|
31 |
+
|
32 |
+
'Inverse',
|
33 |
+
|
34 |
+
'MatAdd',
|
35 |
+
|
36 |
+
'Identity', 'MatrixExpr', 'MatrixSymbol', 'ZeroMatrix', 'OneMatrix',
|
37 |
+
'matrix_symbols', 'MatrixSet',
|
38 |
+
|
39 |
+
'MatMul',
|
40 |
+
|
41 |
+
'MatPow',
|
42 |
+
|
43 |
+
'Trace', 'trace',
|
44 |
+
|
45 |
+
'Determinant', 'det',
|
46 |
+
|
47 |
+
'Transpose',
|
48 |
+
|
49 |
+
'Adjoint',
|
50 |
+
|
51 |
+
'hadamard_product', 'HadamardProduct', 'hadamard_power', 'HadamardPower',
|
52 |
+
|
53 |
+
'DiagonalMatrix', 'DiagonalOf', 'DiagMatrix', 'diagonalize_vector',
|
54 |
+
|
55 |
+
'DotProduct',
|
56 |
+
|
57 |
+
'kronecker_product', 'KroneckerProduct', 'combine_kronecker',
|
58 |
+
|
59 |
+
'PermutationMatrix', 'MatrixPermute',
|
60 |
+
|
61 |
+
'Permanent', 'per'
|
62 |
+
]
|
.venv/lib/python3.11/site-packages/sympy/matrices/expressions/__pycache__/__init__.cpython-311.pyc
ADDED
Binary file (2.24 kB). View file
|
|
.venv/lib/python3.11/site-packages/sympy/matrices/expressions/__pycache__/_shape.cpython-311.pyc
ADDED
Binary file (6.42 kB). View file
|
|
.venv/lib/python3.11/site-packages/sympy/matrices/expressions/__pycache__/adjoint.cpython-311.pyc
ADDED
Binary file (3.28 kB). View file
|
|
.venv/lib/python3.11/site-packages/sympy/matrices/expressions/__pycache__/applyfunc.cpython-311.pyc
ADDED
Binary file (9.18 kB). View file
|
|
.venv/lib/python3.11/site-packages/sympy/matrices/expressions/__pycache__/blockmatrix.cpython-311.pyc
ADDED
Binary file (54.1 kB). View file
|
|
.venv/lib/python3.11/site-packages/sympy/matrices/expressions/__pycache__/companion.cpython-311.pyc
ADDED
Binary file (3.41 kB). View file
|
|
.venv/lib/python3.11/site-packages/sympy/matrices/expressions/__pycache__/diagonal.cpython-311.pyc
ADDED
Binary file (10.5 kB). View file
|
|
.venv/lib/python3.11/site-packages/sympy/matrices/expressions/__pycache__/dotproduct.cpython-311.pyc
ADDED
Binary file (3.17 kB). View file
|
|
.venv/lib/python3.11/site-packages/sympy/matrices/expressions/__pycache__/fourier.cpython-311.pyc
ADDED
Binary file (4.14 kB). View file
|
|
.venv/lib/python3.11/site-packages/sympy/matrices/expressions/__pycache__/funcmatrix.cpython-311.pyc
ADDED
Binary file (5.69 kB). View file
|
|
.venv/lib/python3.11/site-packages/sympy/matrices/expressions/__pycache__/inverse.cpython-311.pyc
ADDED
Binary file (5.54 kB). View file
|
|
.venv/lib/python3.11/site-packages/sympy/matrices/expressions/__pycache__/kronecker.cpython-311.pyc
ADDED
Binary file (24.6 kB). View file
|
|
.venv/lib/python3.11/site-packages/sympy/matrices/expressions/__pycache__/matadd.cpython-311.pyc
ADDED
Binary file (10.4 kB). View file
|
|
.venv/lib/python3.11/site-packages/sympy/matrices/expressions/__pycache__/matpow.cpython-311.pyc
ADDED
Binary file (9.25 kB). View file
|
|
.venv/lib/python3.11/site-packages/sympy/matrices/expressions/__pycache__/special.cpython-311.pyc
ADDED
Binary file (16 kB). View file
|
|
.venv/lib/python3.11/site-packages/sympy/matrices/expressions/adjoint.py
ADDED
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from sympy.core import Basic
|
2 |
+
from sympy.functions import adjoint, conjugate
|
3 |
+
from sympy.matrices.expressions.matexpr import MatrixExpr
|
4 |
+
|
5 |
+
|
6 |
+
class Adjoint(MatrixExpr):
|
7 |
+
"""
|
8 |
+
The Hermitian adjoint of a matrix expression.
|
9 |
+
|
10 |
+
This is a symbolic object that simply stores its argument without
|
11 |
+
evaluating it. To actually compute the adjoint, use the ``adjoint()``
|
12 |
+
function.
|
13 |
+
|
14 |
+
Examples
|
15 |
+
========
|
16 |
+
|
17 |
+
>>> from sympy import MatrixSymbol, Adjoint, adjoint
|
18 |
+
>>> A = MatrixSymbol('A', 3, 5)
|
19 |
+
>>> B = MatrixSymbol('B', 5, 3)
|
20 |
+
>>> Adjoint(A*B)
|
21 |
+
Adjoint(A*B)
|
22 |
+
>>> adjoint(A*B)
|
23 |
+
Adjoint(B)*Adjoint(A)
|
24 |
+
>>> adjoint(A*B) == Adjoint(A*B)
|
25 |
+
False
|
26 |
+
>>> adjoint(A*B) == Adjoint(A*B).doit()
|
27 |
+
True
|
28 |
+
"""
|
29 |
+
is_Adjoint = True
|
30 |
+
|
31 |
+
def doit(self, **hints):
|
32 |
+
arg = self.arg
|
33 |
+
if hints.get('deep', True) and isinstance(arg, Basic):
|
34 |
+
return adjoint(arg.doit(**hints))
|
35 |
+
else:
|
36 |
+
return adjoint(self.arg)
|
37 |
+
|
38 |
+
@property
|
39 |
+
def arg(self):
|
40 |
+
return self.args[0]
|
41 |
+
|
42 |
+
@property
|
43 |
+
def shape(self):
|
44 |
+
return self.arg.shape[::-1]
|
45 |
+
|
46 |
+
def _entry(self, i, j, **kwargs):
|
47 |
+
return conjugate(self.arg._entry(j, i, **kwargs))
|
48 |
+
|
49 |
+
def _eval_adjoint(self):
|
50 |
+
return self.arg
|
51 |
+
|
52 |
+
def _eval_transpose(self):
|
53 |
+
return self.arg.conjugate()
|
54 |
+
|
55 |
+
def _eval_conjugate(self):
|
56 |
+
return self.arg.transpose()
|
57 |
+
|
58 |
+
def _eval_trace(self):
|
59 |
+
from sympy.matrices.expressions.trace import Trace
|
60 |
+
return conjugate(Trace(self.arg))
|
.venv/lib/python3.11/site-packages/sympy/matrices/expressions/applyfunc.py
ADDED
@@ -0,0 +1,204 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from sympy.core.expr import ExprBuilder
|
2 |
+
from sympy.core.function import (Function, FunctionClass, Lambda)
|
3 |
+
from sympy.core.symbol import Dummy
|
4 |
+
from sympy.core.sympify import sympify, _sympify
|
5 |
+
from sympy.matrices.expressions import MatrixExpr
|
6 |
+
from sympy.matrices.matrixbase import MatrixBase
|
7 |
+
|
8 |
+
|
9 |
+
class ElementwiseApplyFunction(MatrixExpr):
|
10 |
+
r"""
|
11 |
+
Apply function to a matrix elementwise without evaluating.
|
12 |
+
|
13 |
+
Examples
|
14 |
+
========
|
15 |
+
|
16 |
+
It can be created by calling ``.applyfunc(<function>)`` on a matrix
|
17 |
+
expression:
|
18 |
+
|
19 |
+
>>> from sympy import MatrixSymbol
|
20 |
+
>>> from sympy.matrices.expressions.applyfunc import ElementwiseApplyFunction
|
21 |
+
>>> from sympy import exp
|
22 |
+
>>> X = MatrixSymbol("X", 3, 3)
|
23 |
+
>>> X.applyfunc(exp)
|
24 |
+
Lambda(_d, exp(_d)).(X)
|
25 |
+
|
26 |
+
Otherwise using the class constructor:
|
27 |
+
|
28 |
+
>>> from sympy import eye
|
29 |
+
>>> expr = ElementwiseApplyFunction(exp, eye(3))
|
30 |
+
>>> expr
|
31 |
+
Lambda(_d, exp(_d)).(Matrix([
|
32 |
+
[1, 0, 0],
|
33 |
+
[0, 1, 0],
|
34 |
+
[0, 0, 1]]))
|
35 |
+
>>> expr.doit()
|
36 |
+
Matrix([
|
37 |
+
[E, 1, 1],
|
38 |
+
[1, E, 1],
|
39 |
+
[1, 1, E]])
|
40 |
+
|
41 |
+
Notice the difference with the real mathematical functions:
|
42 |
+
|
43 |
+
>>> exp(eye(3))
|
44 |
+
Matrix([
|
45 |
+
[E, 0, 0],
|
46 |
+
[0, E, 0],
|
47 |
+
[0, 0, E]])
|
48 |
+
"""
|
49 |
+
|
50 |
+
def __new__(cls, function, expr):
|
51 |
+
expr = _sympify(expr)
|
52 |
+
if not expr.is_Matrix:
|
53 |
+
raise ValueError("{} must be a matrix instance.".format(expr))
|
54 |
+
|
55 |
+
if expr.shape == (1, 1):
|
56 |
+
# Check if the function returns a matrix, in that case, just apply
|
57 |
+
# the function instead of creating an ElementwiseApplyFunc object:
|
58 |
+
ret = function(expr)
|
59 |
+
if isinstance(ret, MatrixExpr):
|
60 |
+
return ret
|
61 |
+
|
62 |
+
if not isinstance(function, (FunctionClass, Lambda)):
|
63 |
+
d = Dummy('d')
|
64 |
+
function = Lambda(d, function(d))
|
65 |
+
|
66 |
+
function = sympify(function)
|
67 |
+
if not isinstance(function, (FunctionClass, Lambda)):
|
68 |
+
raise ValueError(
|
69 |
+
"{} should be compatible with SymPy function classes."
|
70 |
+
.format(function))
|
71 |
+
|
72 |
+
if 1 not in function.nargs:
|
73 |
+
raise ValueError(
|
74 |
+
'{} should be able to accept 1 arguments.'.format(function))
|
75 |
+
|
76 |
+
if not isinstance(function, Lambda):
|
77 |
+
d = Dummy('d')
|
78 |
+
function = Lambda(d, function(d))
|
79 |
+
|
80 |
+
obj = MatrixExpr.__new__(cls, function, expr)
|
81 |
+
return obj
|
82 |
+
|
83 |
+
@property
|
84 |
+
def function(self):
|
85 |
+
return self.args[0]
|
86 |
+
|
87 |
+
@property
|
88 |
+
def expr(self):
|
89 |
+
return self.args[1]
|
90 |
+
|
91 |
+
@property
|
92 |
+
def shape(self):
|
93 |
+
return self.expr.shape
|
94 |
+
|
95 |
+
def doit(self, **hints):
|
96 |
+
deep = hints.get("deep", True)
|
97 |
+
expr = self.expr
|
98 |
+
if deep:
|
99 |
+
expr = expr.doit(**hints)
|
100 |
+
function = self.function
|
101 |
+
if isinstance(function, Lambda) and function.is_identity:
|
102 |
+
# This is a Lambda containing the identity function.
|
103 |
+
return expr
|
104 |
+
if isinstance(expr, MatrixBase):
|
105 |
+
return expr.applyfunc(self.function)
|
106 |
+
elif isinstance(expr, ElementwiseApplyFunction):
|
107 |
+
return ElementwiseApplyFunction(
|
108 |
+
lambda x: self.function(expr.function(x)),
|
109 |
+
expr.expr
|
110 |
+
).doit(**hints)
|
111 |
+
else:
|
112 |
+
return self
|
113 |
+
|
114 |
+
def _entry(self, i, j, **kwargs):
|
115 |
+
return self.function(self.expr._entry(i, j, **kwargs))
|
116 |
+
|
117 |
+
def _get_function_fdiff(self):
|
118 |
+
d = Dummy("d")
|
119 |
+
function = self.function(d)
|
120 |
+
fdiff = function.diff(d)
|
121 |
+
if isinstance(fdiff, Function):
|
122 |
+
fdiff = type(fdiff)
|
123 |
+
else:
|
124 |
+
fdiff = Lambda(d, fdiff)
|
125 |
+
return fdiff
|
126 |
+
|
127 |
+
def _eval_derivative(self, x):
|
128 |
+
from sympy.matrices.expressions.hadamard import hadamard_product
|
129 |
+
dexpr = self.expr.diff(x)
|
130 |
+
fdiff = self._get_function_fdiff()
|
131 |
+
return hadamard_product(
|
132 |
+
dexpr,
|
133 |
+
ElementwiseApplyFunction(fdiff, self.expr)
|
134 |
+
)
|
135 |
+
|
136 |
+
def _eval_derivative_matrix_lines(self, x):
|
137 |
+
from sympy.matrices.expressions.special import Identity
|
138 |
+
from sympy.tensor.array.expressions.array_expressions import ArrayContraction
|
139 |
+
from sympy.tensor.array.expressions.array_expressions import ArrayDiagonal
|
140 |
+
from sympy.tensor.array.expressions.array_expressions import ArrayTensorProduct
|
141 |
+
|
142 |
+
fdiff = self._get_function_fdiff()
|
143 |
+
lr = self.expr._eval_derivative_matrix_lines(x)
|
144 |
+
ewdiff = ElementwiseApplyFunction(fdiff, self.expr)
|
145 |
+
if 1 in x.shape:
|
146 |
+
# Vector:
|
147 |
+
iscolumn = self.shape[1] == 1
|
148 |
+
for i in lr:
|
149 |
+
if iscolumn:
|
150 |
+
ptr1 = i.first_pointer
|
151 |
+
ptr2 = Identity(self.shape[1])
|
152 |
+
else:
|
153 |
+
ptr1 = Identity(self.shape[0])
|
154 |
+
ptr2 = i.second_pointer
|
155 |
+
|
156 |
+
subexpr = ExprBuilder(
|
157 |
+
ArrayDiagonal,
|
158 |
+
[
|
159 |
+
ExprBuilder(
|
160 |
+
ArrayTensorProduct,
|
161 |
+
[
|
162 |
+
ewdiff,
|
163 |
+
ptr1,
|
164 |
+
ptr2,
|
165 |
+
]
|
166 |
+
),
|
167 |
+
(0, 2) if iscolumn else (1, 4)
|
168 |
+
],
|
169 |
+
validator=ArrayDiagonal._validate
|
170 |
+
)
|
171 |
+
i._lines = [subexpr]
|
172 |
+
i._first_pointer_parent = subexpr.args[0].args
|
173 |
+
i._first_pointer_index = 1
|
174 |
+
i._second_pointer_parent = subexpr.args[0].args
|
175 |
+
i._second_pointer_index = 2
|
176 |
+
else:
|
177 |
+
# Matrix case:
|
178 |
+
for i in lr:
|
179 |
+
ptr1 = i.first_pointer
|
180 |
+
ptr2 = i.second_pointer
|
181 |
+
newptr1 = Identity(ptr1.shape[1])
|
182 |
+
newptr2 = Identity(ptr2.shape[1])
|
183 |
+
subexpr = ExprBuilder(
|
184 |
+
ArrayContraction,
|
185 |
+
[
|
186 |
+
ExprBuilder(
|
187 |
+
ArrayTensorProduct,
|
188 |
+
[ptr1, newptr1, ewdiff, ptr2, newptr2]
|
189 |
+
),
|
190 |
+
(1, 2, 4),
|
191 |
+
(5, 7, 8),
|
192 |
+
],
|
193 |
+
validator=ArrayContraction._validate
|
194 |
+
)
|
195 |
+
i._first_pointer_parent = subexpr.args[0].args
|
196 |
+
i._first_pointer_index = 1
|
197 |
+
i._second_pointer_parent = subexpr.args[0].args
|
198 |
+
i._second_pointer_index = 4
|
199 |
+
i._lines = [subexpr]
|
200 |
+
return lr
|
201 |
+
|
202 |
+
def _eval_transpose(self):
|
203 |
+
from sympy.matrices.expressions.transpose import Transpose
|
204 |
+
return self.func(self.function, Transpose(self.expr).doit())
|
.venv/lib/python3.11/site-packages/sympy/matrices/expressions/blockmatrix.py
ADDED
@@ -0,0 +1,980 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from sympy.assumptions.ask import (Q, ask)
|
2 |
+
from sympy.core import Basic, Add, Mul, S
|
3 |
+
from sympy.core.sympify import _sympify
|
4 |
+
from sympy.functions import adjoint
|
5 |
+
from sympy.functions.elementary.complexes import re, im
|
6 |
+
from sympy.strategies import typed, exhaust, condition, do_one, unpack
|
7 |
+
from sympy.strategies.traverse import bottom_up
|
8 |
+
from sympy.utilities.iterables import is_sequence, sift
|
9 |
+
from sympy.utilities.misc import filldedent
|
10 |
+
|
11 |
+
from sympy.matrices import Matrix, ShapeError
|
12 |
+
from sympy.matrices.exceptions import NonInvertibleMatrixError
|
13 |
+
from sympy.matrices.expressions.determinant import det, Determinant
|
14 |
+
from sympy.matrices.expressions.inverse import Inverse
|
15 |
+
from sympy.matrices.expressions.matadd import MatAdd
|
16 |
+
from sympy.matrices.expressions.matexpr import MatrixExpr, MatrixElement
|
17 |
+
from sympy.matrices.expressions.matmul import MatMul
|
18 |
+
from sympy.matrices.expressions.matpow import MatPow
|
19 |
+
from sympy.matrices.expressions.slice import MatrixSlice
|
20 |
+
from sympy.matrices.expressions.special import ZeroMatrix, Identity
|
21 |
+
from sympy.matrices.expressions.trace import trace
|
22 |
+
from sympy.matrices.expressions.transpose import Transpose, transpose
|
23 |
+
|
24 |
+
|
25 |
+
class BlockMatrix(MatrixExpr):
|
26 |
+
"""A BlockMatrix is a Matrix comprised of other matrices.
|
27 |
+
|
28 |
+
The submatrices are stored in a SymPy Matrix object but accessed as part of
|
29 |
+
a Matrix Expression
|
30 |
+
|
31 |
+
>>> from sympy import (MatrixSymbol, BlockMatrix, symbols,
|
32 |
+
... Identity, ZeroMatrix, block_collapse)
|
33 |
+
>>> n,m,l = symbols('n m l')
|
34 |
+
>>> X = MatrixSymbol('X', n, n)
|
35 |
+
>>> Y = MatrixSymbol('Y', m, m)
|
36 |
+
>>> Z = MatrixSymbol('Z', n, m)
|
37 |
+
>>> B = BlockMatrix([[X, Z], [ZeroMatrix(m,n), Y]])
|
38 |
+
>>> print(B)
|
39 |
+
Matrix([
|
40 |
+
[X, Z],
|
41 |
+
[0, Y]])
|
42 |
+
|
43 |
+
>>> C = BlockMatrix([[Identity(n), Z]])
|
44 |
+
>>> print(C)
|
45 |
+
Matrix([[I, Z]])
|
46 |
+
|
47 |
+
>>> print(block_collapse(C*B))
|
48 |
+
Matrix([[X, Z + Z*Y]])
|
49 |
+
|
50 |
+
Some matrices might be comprised of rows of blocks with
|
51 |
+
the matrices in each row having the same height and the
|
52 |
+
rows all having the same total number of columns but
|
53 |
+
not having the same number of columns for each matrix
|
54 |
+
in each row. In this case, the matrix is not a block
|
55 |
+
matrix and should be instantiated by Matrix.
|
56 |
+
|
57 |
+
>>> from sympy import ones, Matrix
|
58 |
+
>>> dat = [
|
59 |
+
... [ones(3,2), ones(3,3)*2],
|
60 |
+
... [ones(2,3)*3, ones(2,2)*4]]
|
61 |
+
...
|
62 |
+
>>> BlockMatrix(dat)
|
63 |
+
Traceback (most recent call last):
|
64 |
+
...
|
65 |
+
ValueError:
|
66 |
+
Although this matrix is comprised of blocks, the blocks do not fill
|
67 |
+
the matrix in a size-symmetric fashion. To create a full matrix from
|
68 |
+
these arguments, pass them directly to Matrix.
|
69 |
+
>>> Matrix(dat)
|
70 |
+
Matrix([
|
71 |
+
[1, 1, 2, 2, 2],
|
72 |
+
[1, 1, 2, 2, 2],
|
73 |
+
[1, 1, 2, 2, 2],
|
74 |
+
[3, 3, 3, 4, 4],
|
75 |
+
[3, 3, 3, 4, 4]])
|
76 |
+
|
77 |
+
See Also
|
78 |
+
========
|
79 |
+
sympy.matrices.matrixbase.MatrixBase.irregular
|
80 |
+
"""
|
81 |
+
def __new__(cls, *args, **kwargs):
|
82 |
+
from sympy.matrices.immutable import ImmutableDenseMatrix
|
83 |
+
isMat = lambda i: getattr(i, 'is_Matrix', False)
|
84 |
+
if len(args) != 1 or \
|
85 |
+
not is_sequence(args[0]) or \
|
86 |
+
len({isMat(r) for r in args[0]}) != 1:
|
87 |
+
raise ValueError(filldedent('''
|
88 |
+
expecting a sequence of 1 or more rows
|
89 |
+
containing Matrices.'''))
|
90 |
+
rows = args[0] if args else []
|
91 |
+
if not isMat(rows):
|
92 |
+
if rows and isMat(rows[0]):
|
93 |
+
rows = [rows] # rows is not list of lists or []
|
94 |
+
# regularity check
|
95 |
+
# same number of matrices in each row
|
96 |
+
blocky = ok = len({len(r) for r in rows}) == 1
|
97 |
+
if ok:
|
98 |
+
# same number of rows for each matrix in a row
|
99 |
+
for r in rows:
|
100 |
+
ok = len({i.rows for i in r}) == 1
|
101 |
+
if not ok:
|
102 |
+
break
|
103 |
+
blocky = ok
|
104 |
+
if ok:
|
105 |
+
# same number of cols for each matrix in each col
|
106 |
+
for c in range(len(rows[0])):
|
107 |
+
ok = len({rows[i][c].cols
|
108 |
+
for i in range(len(rows))}) == 1
|
109 |
+
if not ok:
|
110 |
+
break
|
111 |
+
if not ok:
|
112 |
+
# same total cols in each row
|
113 |
+
ok = len({
|
114 |
+
sum(i.cols for i in r) for r in rows}) == 1
|
115 |
+
if blocky and ok:
|
116 |
+
raise ValueError(filldedent('''
|
117 |
+
Although this matrix is comprised of blocks,
|
118 |
+
the blocks do not fill the matrix in a
|
119 |
+
size-symmetric fashion. To create a full matrix
|
120 |
+
from these arguments, pass them directly to
|
121 |
+
Matrix.'''))
|
122 |
+
raise ValueError(filldedent('''
|
123 |
+
When there are not the same number of rows in each
|
124 |
+
row's matrices or there are not the same number of
|
125 |
+
total columns in each row, the matrix is not a
|
126 |
+
block matrix. If this matrix is known to consist of
|
127 |
+
blocks fully filling a 2-D space then see
|
128 |
+
Matrix.irregular.'''))
|
129 |
+
mat = ImmutableDenseMatrix(rows, evaluate=False)
|
130 |
+
obj = Basic.__new__(cls, mat)
|
131 |
+
return obj
|
132 |
+
|
133 |
+
@property
|
134 |
+
def shape(self):
|
135 |
+
numrows = numcols = 0
|
136 |
+
M = self.blocks
|
137 |
+
for i in range(M.shape[0]):
|
138 |
+
numrows += M[i, 0].shape[0]
|
139 |
+
for i in range(M.shape[1]):
|
140 |
+
numcols += M[0, i].shape[1]
|
141 |
+
return (numrows, numcols)
|
142 |
+
|
143 |
+
@property
|
144 |
+
def blockshape(self):
|
145 |
+
return self.blocks.shape
|
146 |
+
|
147 |
+
@property
|
148 |
+
def blocks(self):
|
149 |
+
return self.args[0]
|
150 |
+
|
151 |
+
@property
|
152 |
+
def rowblocksizes(self):
|
153 |
+
return [self.blocks[i, 0].rows for i in range(self.blockshape[0])]
|
154 |
+
|
155 |
+
@property
|
156 |
+
def colblocksizes(self):
|
157 |
+
return [self.blocks[0, i].cols for i in range(self.blockshape[1])]
|
158 |
+
|
159 |
+
def structurally_equal(self, other):
|
160 |
+
return (isinstance(other, BlockMatrix)
|
161 |
+
and self.shape == other.shape
|
162 |
+
and self.blockshape == other.blockshape
|
163 |
+
and self.rowblocksizes == other.rowblocksizes
|
164 |
+
and self.colblocksizes == other.colblocksizes)
|
165 |
+
|
166 |
+
def _blockmul(self, other):
|
167 |
+
if (isinstance(other, BlockMatrix) and
|
168 |
+
self.colblocksizes == other.rowblocksizes):
|
169 |
+
return BlockMatrix(self.blocks*other.blocks)
|
170 |
+
|
171 |
+
return self * other
|
172 |
+
|
173 |
+
def _blockadd(self, other):
|
174 |
+
if (isinstance(other, BlockMatrix)
|
175 |
+
and self.structurally_equal(other)):
|
176 |
+
return BlockMatrix(self.blocks + other.blocks)
|
177 |
+
|
178 |
+
return self + other
|
179 |
+
|
180 |
+
def _eval_transpose(self):
|
181 |
+
# Flip all the individual matrices
|
182 |
+
matrices = [transpose(matrix) for matrix in self.blocks]
|
183 |
+
# Make a copy
|
184 |
+
M = Matrix(self.blockshape[0], self.blockshape[1], matrices)
|
185 |
+
# Transpose the block structure
|
186 |
+
M = M.transpose()
|
187 |
+
return BlockMatrix(M)
|
188 |
+
|
189 |
+
def _eval_adjoint(self):
|
190 |
+
# Adjoint all the individual matrices
|
191 |
+
matrices = [adjoint(matrix) for matrix in self.blocks]
|
192 |
+
# Make a copy
|
193 |
+
M = Matrix(self.blockshape[0], self.blockshape[1], matrices)
|
194 |
+
# Transpose the block structure
|
195 |
+
M = M.transpose()
|
196 |
+
return BlockMatrix(M)
|
197 |
+
|
198 |
+
def _eval_trace(self):
|
199 |
+
if self.rowblocksizes == self.colblocksizes:
|
200 |
+
blocks = [self.blocks[i, i] for i in range(self.blockshape[0])]
|
201 |
+
return Add(*[trace(block) for block in blocks])
|
202 |
+
|
203 |
+
def _eval_determinant(self):
|
204 |
+
if self.blockshape == (1, 1):
|
205 |
+
return det(self.blocks[0, 0])
|
206 |
+
if self.blockshape == (2, 2):
|
207 |
+
[[A, B],
|
208 |
+
[C, D]] = self.blocks.tolist()
|
209 |
+
if ask(Q.invertible(A)):
|
210 |
+
return det(A)*det(D - C*A.I*B)
|
211 |
+
elif ask(Q.invertible(D)):
|
212 |
+
return det(D)*det(A - B*D.I*C)
|
213 |
+
return Determinant(self)
|
214 |
+
|
215 |
+
def _eval_as_real_imag(self):
|
216 |
+
real_matrices = [re(matrix) for matrix in self.blocks]
|
217 |
+
real_matrices = Matrix(self.blockshape[0], self.blockshape[1], real_matrices)
|
218 |
+
|
219 |
+
im_matrices = [im(matrix) for matrix in self.blocks]
|
220 |
+
im_matrices = Matrix(self.blockshape[0], self.blockshape[1], im_matrices)
|
221 |
+
|
222 |
+
return (BlockMatrix(real_matrices), BlockMatrix(im_matrices))
|
223 |
+
|
224 |
+
def _eval_derivative(self, x):
|
225 |
+
return BlockMatrix(self.blocks.diff(x))
|
226 |
+
|
227 |
+
def transpose(self):
|
228 |
+
"""Return transpose of matrix.
|
229 |
+
|
230 |
+
Examples
|
231 |
+
========
|
232 |
+
|
233 |
+
>>> from sympy import MatrixSymbol, BlockMatrix, ZeroMatrix
|
234 |
+
>>> from sympy.abc import m, n
|
235 |
+
>>> X = MatrixSymbol('X', n, n)
|
236 |
+
>>> Y = MatrixSymbol('Y', m, m)
|
237 |
+
>>> Z = MatrixSymbol('Z', n, m)
|
238 |
+
>>> B = BlockMatrix([[X, Z], [ZeroMatrix(m,n), Y]])
|
239 |
+
>>> B.transpose()
|
240 |
+
Matrix([
|
241 |
+
[X.T, 0],
|
242 |
+
[Z.T, Y.T]])
|
243 |
+
>>> _.transpose()
|
244 |
+
Matrix([
|
245 |
+
[X, Z],
|
246 |
+
[0, Y]])
|
247 |
+
"""
|
248 |
+
return self._eval_transpose()
|
249 |
+
|
250 |
+
def schur(self, mat = 'A', generalized = False):
|
251 |
+
"""Return the Schur Complement of the 2x2 BlockMatrix
|
252 |
+
|
253 |
+
Parameters
|
254 |
+
==========
|
255 |
+
|
256 |
+
mat : String, optional
|
257 |
+
The matrix with respect to which the
|
258 |
+
Schur Complement is calculated. 'A' is
|
259 |
+
used by default
|
260 |
+
|
261 |
+
generalized : bool, optional
|
262 |
+
If True, returns the generalized Schur
|
263 |
+
Component which uses Moore-Penrose Inverse
|
264 |
+
|
265 |
+
Examples
|
266 |
+
========
|
267 |
+
|
268 |
+
>>> from sympy import symbols, MatrixSymbol, BlockMatrix
|
269 |
+
>>> m, n = symbols('m n')
|
270 |
+
>>> A = MatrixSymbol('A', n, n)
|
271 |
+
>>> B = MatrixSymbol('B', n, m)
|
272 |
+
>>> C = MatrixSymbol('C', m, n)
|
273 |
+
>>> D = MatrixSymbol('D', m, m)
|
274 |
+
>>> X = BlockMatrix([[A, B], [C, D]])
|
275 |
+
|
276 |
+
The default Schur Complement is evaluated with "A"
|
277 |
+
|
278 |
+
>>> X.schur()
|
279 |
+
-C*A**(-1)*B + D
|
280 |
+
>>> X.schur('D')
|
281 |
+
A - B*D**(-1)*C
|
282 |
+
|
283 |
+
Schur complement with non-invertible matrices is not
|
284 |
+
defined. Instead, the generalized Schur complement can
|
285 |
+
be calculated which uses the Moore-Penrose Inverse. To
|
286 |
+
achieve this, `generalized` must be set to `True`
|
287 |
+
|
288 |
+
>>> X.schur('B', generalized=True)
|
289 |
+
C - D*(B.T*B)**(-1)*B.T*A
|
290 |
+
>>> X.schur('C', generalized=True)
|
291 |
+
-A*(C.T*C)**(-1)*C.T*D + B
|
292 |
+
|
293 |
+
Returns
|
294 |
+
=======
|
295 |
+
|
296 |
+
M : Matrix
|
297 |
+
The Schur Complement Matrix
|
298 |
+
|
299 |
+
Raises
|
300 |
+
======
|
301 |
+
|
302 |
+
ShapeError
|
303 |
+
If the block matrix is not a 2x2 matrix
|
304 |
+
|
305 |
+
NonInvertibleMatrixError
|
306 |
+
If given matrix is non-invertible
|
307 |
+
|
308 |
+
References
|
309 |
+
==========
|
310 |
+
|
311 |
+
.. [1] Wikipedia Article on Schur Component : https://en.wikipedia.org/wiki/Schur_complement
|
312 |
+
|
313 |
+
See Also
|
314 |
+
========
|
315 |
+
|
316 |
+
sympy.matrices.matrixbase.MatrixBase.pinv
|
317 |
+
"""
|
318 |
+
|
319 |
+
if self.blockshape == (2, 2):
|
320 |
+
[[A, B],
|
321 |
+
[C, D]] = self.blocks.tolist()
|
322 |
+
d={'A' : A, 'B' : B, 'C' : C, 'D' : D}
|
323 |
+
try:
|
324 |
+
inv = (d[mat].T*d[mat]).inv()*d[mat].T if generalized else d[mat].inv()
|
325 |
+
if mat == 'A':
|
326 |
+
return D - C * inv * B
|
327 |
+
elif mat == 'B':
|
328 |
+
return C - D * inv * A
|
329 |
+
elif mat == 'C':
|
330 |
+
return B - A * inv * D
|
331 |
+
elif mat == 'D':
|
332 |
+
return A - B * inv * C
|
333 |
+
#For matrices where no sub-matrix is square
|
334 |
+
return self
|
335 |
+
except NonInvertibleMatrixError:
|
336 |
+
raise NonInvertibleMatrixError('The given matrix is not invertible. Please set generalized=True \
|
337 |
+
to compute the generalized Schur Complement which uses Moore-Penrose Inverse')
|
338 |
+
else:
|
339 |
+
raise ShapeError('Schur Complement can only be calculated for 2x2 block matrices')
|
340 |
+
|
341 |
+
def LDUdecomposition(self):
|
342 |
+
"""Returns the Block LDU decomposition of
|
343 |
+
a 2x2 Block Matrix
|
344 |
+
|
345 |
+
Returns
|
346 |
+
=======
|
347 |
+
|
348 |
+
(L, D, U) : Matrices
|
349 |
+
L : Lower Diagonal Matrix
|
350 |
+
D : Diagonal Matrix
|
351 |
+
U : Upper Diagonal Matrix
|
352 |
+
|
353 |
+
Examples
|
354 |
+
========
|
355 |
+
|
356 |
+
>>> from sympy import symbols, MatrixSymbol, BlockMatrix, block_collapse
|
357 |
+
>>> m, n = symbols('m n')
|
358 |
+
>>> A = MatrixSymbol('A', n, n)
|
359 |
+
>>> B = MatrixSymbol('B', n, m)
|
360 |
+
>>> C = MatrixSymbol('C', m, n)
|
361 |
+
>>> D = MatrixSymbol('D', m, m)
|
362 |
+
>>> X = BlockMatrix([[A, B], [C, D]])
|
363 |
+
>>> L, D, U = X.LDUdecomposition()
|
364 |
+
>>> block_collapse(L*D*U)
|
365 |
+
Matrix([
|
366 |
+
[A, B],
|
367 |
+
[C, D]])
|
368 |
+
|
369 |
+
Raises
|
370 |
+
======
|
371 |
+
|
372 |
+
ShapeError
|
373 |
+
If the block matrix is not a 2x2 matrix
|
374 |
+
|
375 |
+
NonInvertibleMatrixError
|
376 |
+
If the matrix "A" is non-invertible
|
377 |
+
|
378 |
+
See Also
|
379 |
+
========
|
380 |
+
sympy.matrices.expressions.blockmatrix.BlockMatrix.UDLdecomposition
|
381 |
+
sympy.matrices.expressions.blockmatrix.BlockMatrix.LUdecomposition
|
382 |
+
"""
|
383 |
+
if self.blockshape == (2,2):
|
384 |
+
[[A, B],
|
385 |
+
[C, D]] = self.blocks.tolist()
|
386 |
+
try:
|
387 |
+
AI = A.I
|
388 |
+
except NonInvertibleMatrixError:
|
389 |
+
raise NonInvertibleMatrixError('Block LDU decomposition cannot be calculated when\
|
390 |
+
"A" is singular')
|
391 |
+
Ip = Identity(B.shape[0])
|
392 |
+
Iq = Identity(B.shape[1])
|
393 |
+
Z = ZeroMatrix(*B.shape)
|
394 |
+
L = BlockMatrix([[Ip, Z], [C*AI, Iq]])
|
395 |
+
D = BlockDiagMatrix(A, self.schur())
|
396 |
+
U = BlockMatrix([[Ip, AI*B],[Z.T, Iq]])
|
397 |
+
return L, D, U
|
398 |
+
else:
|
399 |
+
raise ShapeError("Block LDU decomposition is supported only for 2x2 block matrices")
|
400 |
+
|
401 |
+
def UDLdecomposition(self):
|
402 |
+
"""Returns the Block UDL decomposition of
|
403 |
+
a 2x2 Block Matrix
|
404 |
+
|
405 |
+
Returns
|
406 |
+
=======
|
407 |
+
|
408 |
+
(U, D, L) : Matrices
|
409 |
+
U : Upper Diagonal Matrix
|
410 |
+
D : Diagonal Matrix
|
411 |
+
L : Lower Diagonal Matrix
|
412 |
+
|
413 |
+
Examples
|
414 |
+
========
|
415 |
+
|
416 |
+
>>> from sympy import symbols, MatrixSymbol, BlockMatrix, block_collapse
|
417 |
+
>>> m, n = symbols('m n')
|
418 |
+
>>> A = MatrixSymbol('A', n, n)
|
419 |
+
>>> B = MatrixSymbol('B', n, m)
|
420 |
+
>>> C = MatrixSymbol('C', m, n)
|
421 |
+
>>> D = MatrixSymbol('D', m, m)
|
422 |
+
>>> X = BlockMatrix([[A, B], [C, D]])
|
423 |
+
>>> U, D, L = X.UDLdecomposition()
|
424 |
+
>>> block_collapse(U*D*L)
|
425 |
+
Matrix([
|
426 |
+
[A, B],
|
427 |
+
[C, D]])
|
428 |
+
|
429 |
+
Raises
|
430 |
+
======
|
431 |
+
|
432 |
+
ShapeError
|
433 |
+
If the block matrix is not a 2x2 matrix
|
434 |
+
|
435 |
+
NonInvertibleMatrixError
|
436 |
+
If the matrix "D" is non-invertible
|
437 |
+
|
438 |
+
See Also
|
439 |
+
========
|
440 |
+
sympy.matrices.expressions.blockmatrix.BlockMatrix.LDUdecomposition
|
441 |
+
sympy.matrices.expressions.blockmatrix.BlockMatrix.LUdecomposition
|
442 |
+
"""
|
443 |
+
if self.blockshape == (2,2):
|
444 |
+
[[A, B],
|
445 |
+
[C, D]] = self.blocks.tolist()
|
446 |
+
try:
|
447 |
+
DI = D.I
|
448 |
+
except NonInvertibleMatrixError:
|
449 |
+
raise NonInvertibleMatrixError('Block UDL decomposition cannot be calculated when\
|
450 |
+
"D" is singular')
|
451 |
+
Ip = Identity(A.shape[0])
|
452 |
+
Iq = Identity(B.shape[1])
|
453 |
+
Z = ZeroMatrix(*B.shape)
|
454 |
+
U = BlockMatrix([[Ip, B*DI], [Z.T, Iq]])
|
455 |
+
D = BlockDiagMatrix(self.schur('D'), D)
|
456 |
+
L = BlockMatrix([[Ip, Z],[DI*C, Iq]])
|
457 |
+
return U, D, L
|
458 |
+
else:
|
459 |
+
raise ShapeError("Block UDL decomposition is supported only for 2x2 block matrices")
|
460 |
+
|
461 |
+
def LUdecomposition(self):
|
462 |
+
"""Returns the Block LU decomposition of
|
463 |
+
a 2x2 Block Matrix
|
464 |
+
|
465 |
+
Returns
|
466 |
+
=======
|
467 |
+
|
468 |
+
(L, U) : Matrices
|
469 |
+
L : Lower Diagonal Matrix
|
470 |
+
U : Upper Diagonal Matrix
|
471 |
+
|
472 |
+
Examples
|
473 |
+
========
|
474 |
+
|
475 |
+
>>> from sympy import symbols, MatrixSymbol, BlockMatrix, block_collapse
|
476 |
+
>>> m, n = symbols('m n')
|
477 |
+
>>> A = MatrixSymbol('A', n, n)
|
478 |
+
>>> B = MatrixSymbol('B', n, m)
|
479 |
+
>>> C = MatrixSymbol('C', m, n)
|
480 |
+
>>> D = MatrixSymbol('D', m, m)
|
481 |
+
>>> X = BlockMatrix([[A, B], [C, D]])
|
482 |
+
>>> L, U = X.LUdecomposition()
|
483 |
+
>>> block_collapse(L*U)
|
484 |
+
Matrix([
|
485 |
+
[A, B],
|
486 |
+
[C, D]])
|
487 |
+
|
488 |
+
Raises
|
489 |
+
======
|
490 |
+
|
491 |
+
ShapeError
|
492 |
+
If the block matrix is not a 2x2 matrix
|
493 |
+
|
494 |
+
NonInvertibleMatrixError
|
495 |
+
If the matrix "A" is non-invertible
|
496 |
+
|
497 |
+
See Also
|
498 |
+
========
|
499 |
+
sympy.matrices.expressions.blockmatrix.BlockMatrix.UDLdecomposition
|
500 |
+
sympy.matrices.expressions.blockmatrix.BlockMatrix.LDUdecomposition
|
501 |
+
"""
|
502 |
+
if self.blockshape == (2,2):
|
503 |
+
[[A, B],
|
504 |
+
[C, D]] = self.blocks.tolist()
|
505 |
+
try:
|
506 |
+
A = A**S.Half
|
507 |
+
AI = A.I
|
508 |
+
except NonInvertibleMatrixError:
|
509 |
+
raise NonInvertibleMatrixError('Block LU decomposition cannot be calculated when\
|
510 |
+
"A" is singular')
|
511 |
+
Z = ZeroMatrix(*B.shape)
|
512 |
+
Q = self.schur()**S.Half
|
513 |
+
L = BlockMatrix([[A, Z], [C*AI, Q]])
|
514 |
+
U = BlockMatrix([[A, AI*B],[Z.T, Q]])
|
515 |
+
return L, U
|
516 |
+
else:
|
517 |
+
raise ShapeError("Block LU decomposition is supported only for 2x2 block matrices")
|
518 |
+
|
519 |
+
def _entry(self, i, j, **kwargs):
|
520 |
+
# Find row entry
|
521 |
+
orig_i, orig_j = i, j
|
522 |
+
for row_block, numrows in enumerate(self.rowblocksizes):
|
523 |
+
cmp = i < numrows
|
524 |
+
if cmp == True:
|
525 |
+
break
|
526 |
+
elif cmp == False:
|
527 |
+
i -= numrows
|
528 |
+
elif row_block < self.blockshape[0] - 1:
|
529 |
+
# Can't tell which block and it's not the last one, return unevaluated
|
530 |
+
return MatrixElement(self, orig_i, orig_j)
|
531 |
+
for col_block, numcols in enumerate(self.colblocksizes):
|
532 |
+
cmp = j < numcols
|
533 |
+
if cmp == True:
|
534 |
+
break
|
535 |
+
elif cmp == False:
|
536 |
+
j -= numcols
|
537 |
+
elif col_block < self.blockshape[1] - 1:
|
538 |
+
return MatrixElement(self, orig_i, orig_j)
|
539 |
+
return self.blocks[row_block, col_block][i, j]
|
540 |
+
|
541 |
+
@property
|
542 |
+
def is_Identity(self):
|
543 |
+
if self.blockshape[0] != self.blockshape[1]:
|
544 |
+
return False
|
545 |
+
for i in range(self.blockshape[0]):
|
546 |
+
for j in range(self.blockshape[1]):
|
547 |
+
if i==j and not self.blocks[i, j].is_Identity:
|
548 |
+
return False
|
549 |
+
if i!=j and not self.blocks[i, j].is_ZeroMatrix:
|
550 |
+
return False
|
551 |
+
return True
|
552 |
+
|
553 |
+
@property
|
554 |
+
def is_structurally_symmetric(self):
|
555 |
+
return self.rowblocksizes == self.colblocksizes
|
556 |
+
|
557 |
+
def equals(self, other):
|
558 |
+
if self == other:
|
559 |
+
return True
|
560 |
+
if (isinstance(other, BlockMatrix) and self.blocks == other.blocks):
|
561 |
+
return True
|
562 |
+
return super().equals(other)
|
563 |
+
|
564 |
+
|
565 |
+
class BlockDiagMatrix(BlockMatrix):
|
566 |
+
"""A sparse matrix with block matrices along its diagonals
|
567 |
+
|
568 |
+
Examples
|
569 |
+
========
|
570 |
+
|
571 |
+
>>> from sympy import MatrixSymbol, BlockDiagMatrix, symbols
|
572 |
+
>>> n, m, l = symbols('n m l')
|
573 |
+
>>> X = MatrixSymbol('X', n, n)
|
574 |
+
>>> Y = MatrixSymbol('Y', m, m)
|
575 |
+
>>> BlockDiagMatrix(X, Y)
|
576 |
+
Matrix([
|
577 |
+
[X, 0],
|
578 |
+
[0, Y]])
|
579 |
+
|
580 |
+
Notes
|
581 |
+
=====
|
582 |
+
|
583 |
+
If you want to get the individual diagonal blocks, use
|
584 |
+
:meth:`get_diag_blocks`.
|
585 |
+
|
586 |
+
See Also
|
587 |
+
========
|
588 |
+
|
589 |
+
sympy.matrices.dense.diag
|
590 |
+
"""
|
591 |
+
def __new__(cls, *mats):
|
592 |
+
return Basic.__new__(BlockDiagMatrix, *[_sympify(m) for m in mats])
|
593 |
+
|
594 |
+
@property
|
595 |
+
def diag(self):
|
596 |
+
return self.args
|
597 |
+
|
598 |
+
@property
|
599 |
+
def blocks(self):
|
600 |
+
from sympy.matrices.immutable import ImmutableDenseMatrix
|
601 |
+
mats = self.args
|
602 |
+
data = [[mats[i] if i == j else ZeroMatrix(mats[i].rows, mats[j].cols)
|
603 |
+
for j in range(len(mats))]
|
604 |
+
for i in range(len(mats))]
|
605 |
+
return ImmutableDenseMatrix(data, evaluate=False)
|
606 |
+
|
607 |
+
@property
|
608 |
+
def shape(self):
|
609 |
+
return (sum(block.rows for block in self.args),
|
610 |
+
sum(block.cols for block in self.args))
|
611 |
+
|
612 |
+
@property
|
613 |
+
def blockshape(self):
|
614 |
+
n = len(self.args)
|
615 |
+
return (n, n)
|
616 |
+
|
617 |
+
@property
|
618 |
+
def rowblocksizes(self):
|
619 |
+
return [block.rows for block in self.args]
|
620 |
+
|
621 |
+
@property
|
622 |
+
def colblocksizes(self):
|
623 |
+
return [block.cols for block in self.args]
|
624 |
+
|
625 |
+
def _all_square_blocks(self):
|
626 |
+
"""Returns true if all blocks are square"""
|
627 |
+
return all(mat.is_square for mat in self.args)
|
628 |
+
|
629 |
+
def _eval_determinant(self):
|
630 |
+
if self._all_square_blocks():
|
631 |
+
return Mul(*[det(mat) for mat in self.args])
|
632 |
+
# At least one block is non-square. Since the entire matrix must be square we know there must
|
633 |
+
# be at least two blocks in this matrix, in which case the entire matrix is necessarily rank-deficient
|
634 |
+
return S.Zero
|
635 |
+
|
636 |
+
def _eval_inverse(self, expand='ignored'):
|
637 |
+
if self._all_square_blocks():
|
638 |
+
return BlockDiagMatrix(*[mat.inverse() for mat in self.args])
|
639 |
+
# See comment in _eval_determinant()
|
640 |
+
raise NonInvertibleMatrixError('Matrix det == 0; not invertible.')
|
641 |
+
|
642 |
+
def _eval_transpose(self):
|
643 |
+
return BlockDiagMatrix(*[mat.transpose() for mat in self.args])
|
644 |
+
|
645 |
+
def _blockmul(self, other):
|
646 |
+
if (isinstance(other, BlockDiagMatrix) and
|
647 |
+
self.colblocksizes == other.rowblocksizes):
|
648 |
+
return BlockDiagMatrix(*[a*b for a, b in zip(self.args, other.args)])
|
649 |
+
else:
|
650 |
+
return BlockMatrix._blockmul(self, other)
|
651 |
+
|
652 |
+
def _blockadd(self, other):
|
653 |
+
if (isinstance(other, BlockDiagMatrix) and
|
654 |
+
self.blockshape == other.blockshape and
|
655 |
+
self.rowblocksizes == other.rowblocksizes and
|
656 |
+
self.colblocksizes == other.colblocksizes):
|
657 |
+
return BlockDiagMatrix(*[a + b for a, b in zip(self.args, other.args)])
|
658 |
+
else:
|
659 |
+
return BlockMatrix._blockadd(self, other)
|
660 |
+
|
661 |
+
def get_diag_blocks(self):
|
662 |
+
"""Return the list of diagonal blocks of the matrix.
|
663 |
+
|
664 |
+
Examples
|
665 |
+
========
|
666 |
+
|
667 |
+
>>> from sympy import BlockDiagMatrix, Matrix
|
668 |
+
|
669 |
+
>>> A = Matrix([[1, 2], [3, 4]])
|
670 |
+
>>> B = Matrix([[5, 6], [7, 8]])
|
671 |
+
>>> M = BlockDiagMatrix(A, B)
|
672 |
+
|
673 |
+
How to get diagonal blocks from the block diagonal matrix:
|
674 |
+
|
675 |
+
>>> diag_blocks = M.get_diag_blocks()
|
676 |
+
>>> diag_blocks[0]
|
677 |
+
Matrix([
|
678 |
+
[1, 2],
|
679 |
+
[3, 4]])
|
680 |
+
>>> diag_blocks[1]
|
681 |
+
Matrix([
|
682 |
+
[5, 6],
|
683 |
+
[7, 8]])
|
684 |
+
"""
|
685 |
+
return self.args
|
686 |
+
|
687 |
+
|
688 |
+
def block_collapse(expr):
|
689 |
+
"""Evaluates a block matrix expression
|
690 |
+
|
691 |
+
>>> from sympy import MatrixSymbol, BlockMatrix, symbols, Identity, ZeroMatrix, block_collapse
|
692 |
+
>>> n,m,l = symbols('n m l')
|
693 |
+
>>> X = MatrixSymbol('X', n, n)
|
694 |
+
>>> Y = MatrixSymbol('Y', m, m)
|
695 |
+
>>> Z = MatrixSymbol('Z', n, m)
|
696 |
+
>>> B = BlockMatrix([[X, Z], [ZeroMatrix(m, n), Y]])
|
697 |
+
>>> print(B)
|
698 |
+
Matrix([
|
699 |
+
[X, Z],
|
700 |
+
[0, Y]])
|
701 |
+
|
702 |
+
>>> C = BlockMatrix([[Identity(n), Z]])
|
703 |
+
>>> print(C)
|
704 |
+
Matrix([[I, Z]])
|
705 |
+
|
706 |
+
>>> print(block_collapse(C*B))
|
707 |
+
Matrix([[X, Z + Z*Y]])
|
708 |
+
"""
|
709 |
+
from sympy.strategies.util import expr_fns
|
710 |
+
|
711 |
+
hasbm = lambda expr: isinstance(expr, MatrixExpr) and expr.has(BlockMatrix)
|
712 |
+
|
713 |
+
conditioned_rl = condition(
|
714 |
+
hasbm,
|
715 |
+
typed(
|
716 |
+
{MatAdd: do_one(bc_matadd, bc_block_plus_ident),
|
717 |
+
MatMul: do_one(bc_matmul, bc_dist),
|
718 |
+
MatPow: bc_matmul,
|
719 |
+
Transpose: bc_transpose,
|
720 |
+
Inverse: bc_inverse,
|
721 |
+
BlockMatrix: do_one(bc_unpack, deblock)}
|
722 |
+
)
|
723 |
+
)
|
724 |
+
|
725 |
+
rule = exhaust(
|
726 |
+
bottom_up(
|
727 |
+
exhaust(conditioned_rl),
|
728 |
+
fns=expr_fns
|
729 |
+
)
|
730 |
+
)
|
731 |
+
|
732 |
+
result = rule(expr)
|
733 |
+
doit = getattr(result, 'doit', None)
|
734 |
+
if doit is not None:
|
735 |
+
return doit()
|
736 |
+
else:
|
737 |
+
return result
|
738 |
+
|
739 |
+
def bc_unpack(expr):
|
740 |
+
if expr.blockshape == (1, 1):
|
741 |
+
return expr.blocks[0, 0]
|
742 |
+
return expr
|
743 |
+
|
744 |
+
def bc_matadd(expr):
|
745 |
+
args = sift(expr.args, lambda M: isinstance(M, BlockMatrix))
|
746 |
+
blocks = args[True]
|
747 |
+
if not blocks:
|
748 |
+
return expr
|
749 |
+
|
750 |
+
nonblocks = args[False]
|
751 |
+
block = blocks[0]
|
752 |
+
for b in blocks[1:]:
|
753 |
+
block = block._blockadd(b)
|
754 |
+
if nonblocks:
|
755 |
+
return MatAdd(*nonblocks) + block
|
756 |
+
else:
|
757 |
+
return block
|
758 |
+
|
759 |
+
def bc_block_plus_ident(expr):
|
760 |
+
idents = [arg for arg in expr.args if arg.is_Identity]
|
761 |
+
if not idents:
|
762 |
+
return expr
|
763 |
+
|
764 |
+
blocks = [arg for arg in expr.args if isinstance(arg, BlockMatrix)]
|
765 |
+
if (blocks and all(b.structurally_equal(blocks[0]) for b in blocks)
|
766 |
+
and blocks[0].is_structurally_symmetric):
|
767 |
+
block_id = BlockDiagMatrix(*[Identity(k)
|
768 |
+
for k in blocks[0].rowblocksizes])
|
769 |
+
rest = [arg for arg in expr.args if not arg.is_Identity and not isinstance(arg, BlockMatrix)]
|
770 |
+
return MatAdd(block_id * len(idents), *blocks, *rest).doit()
|
771 |
+
|
772 |
+
return expr
|
773 |
+
|
774 |
+
def bc_dist(expr):
|
775 |
+
""" Turn a*[X, Y] into [a*X, a*Y] """
|
776 |
+
factor, mat = expr.as_coeff_mmul()
|
777 |
+
if factor == 1:
|
778 |
+
return expr
|
779 |
+
|
780 |
+
unpacked = unpack(mat)
|
781 |
+
|
782 |
+
if isinstance(unpacked, BlockDiagMatrix):
|
783 |
+
B = unpacked.diag
|
784 |
+
new_B = [factor * mat for mat in B]
|
785 |
+
return BlockDiagMatrix(*new_B)
|
786 |
+
elif isinstance(unpacked, BlockMatrix):
|
787 |
+
B = unpacked.blocks
|
788 |
+
new_B = [
|
789 |
+
[factor * B[i, j] for j in range(B.cols)] for i in range(B.rows)]
|
790 |
+
return BlockMatrix(new_B)
|
791 |
+
return expr
|
792 |
+
|
793 |
+
|
794 |
+
def bc_matmul(expr):
|
795 |
+
if isinstance(expr, MatPow):
|
796 |
+
if expr.args[1].is_Integer and expr.args[1] > 0:
|
797 |
+
factor, matrices = 1, [expr.args[0]]*expr.args[1]
|
798 |
+
else:
|
799 |
+
return expr
|
800 |
+
else:
|
801 |
+
factor, matrices = expr.as_coeff_matrices()
|
802 |
+
|
803 |
+
i = 0
|
804 |
+
while (i+1 < len(matrices)):
|
805 |
+
A, B = matrices[i:i+2]
|
806 |
+
if isinstance(A, BlockMatrix) and isinstance(B, BlockMatrix):
|
807 |
+
matrices[i] = A._blockmul(B)
|
808 |
+
matrices.pop(i+1)
|
809 |
+
elif isinstance(A, BlockMatrix):
|
810 |
+
matrices[i] = A._blockmul(BlockMatrix([[B]]))
|
811 |
+
matrices.pop(i+1)
|
812 |
+
elif isinstance(B, BlockMatrix):
|
813 |
+
matrices[i] = BlockMatrix([[A]])._blockmul(B)
|
814 |
+
matrices.pop(i+1)
|
815 |
+
else:
|
816 |
+
i+=1
|
817 |
+
return MatMul(factor, *matrices).doit()
|
818 |
+
|
819 |
+
def bc_transpose(expr):
|
820 |
+
collapse = block_collapse(expr.arg)
|
821 |
+
return collapse._eval_transpose()
|
822 |
+
|
823 |
+
|
824 |
+
def bc_inverse(expr):
|
825 |
+
if isinstance(expr.arg, BlockDiagMatrix):
|
826 |
+
return expr.inverse()
|
827 |
+
|
828 |
+
expr2 = blockinverse_1x1(expr)
|
829 |
+
if expr != expr2:
|
830 |
+
return expr2
|
831 |
+
return blockinverse_2x2(Inverse(reblock_2x2(expr.arg)))
|
832 |
+
|
833 |
+
def blockinverse_1x1(expr):
|
834 |
+
if isinstance(expr.arg, BlockMatrix) and expr.arg.blockshape == (1, 1):
|
835 |
+
mat = Matrix([[expr.arg.blocks[0].inverse()]])
|
836 |
+
return BlockMatrix(mat)
|
837 |
+
return expr
|
838 |
+
|
839 |
+
|
840 |
+
def blockinverse_2x2(expr):
|
841 |
+
if isinstance(expr.arg, BlockMatrix) and expr.arg.blockshape == (2, 2):
|
842 |
+
# See: Inverses of 2x2 Block Matrices, Tzon-Tzer Lu and Sheng-Hua Shiou
|
843 |
+
[[A, B],
|
844 |
+
[C, D]] = expr.arg.blocks.tolist()
|
845 |
+
|
846 |
+
formula = _choose_2x2_inversion_formula(A, B, C, D)
|
847 |
+
if formula != None:
|
848 |
+
MI = expr.arg.schur(formula).I
|
849 |
+
if formula == 'A':
|
850 |
+
AI = A.I
|
851 |
+
return BlockMatrix([[AI + AI * B * MI * C * AI, -AI * B * MI], [-MI * C * AI, MI]])
|
852 |
+
if formula == 'B':
|
853 |
+
BI = B.I
|
854 |
+
return BlockMatrix([[-MI * D * BI, MI], [BI + BI * A * MI * D * BI, -BI * A * MI]])
|
855 |
+
if formula == 'C':
|
856 |
+
CI = C.I
|
857 |
+
return BlockMatrix([[-CI * D * MI, CI + CI * D * MI * A * CI], [MI, -MI * A * CI]])
|
858 |
+
if formula == 'D':
|
859 |
+
DI = D.I
|
860 |
+
return BlockMatrix([[MI, -MI * B * DI], [-DI * C * MI, DI + DI * C * MI * B * DI]])
|
861 |
+
|
862 |
+
return expr
|
863 |
+
|
864 |
+
|
865 |
+
def _choose_2x2_inversion_formula(A, B, C, D):
|
866 |
+
"""
|
867 |
+
Assuming [[A, B], [C, D]] would form a valid square block matrix, find
|
868 |
+
which of the classical 2x2 block matrix inversion formulas would be
|
869 |
+
best suited.
|
870 |
+
|
871 |
+
Returns 'A', 'B', 'C', 'D' to represent the algorithm involving inversion
|
872 |
+
of the given argument or None if the matrix cannot be inverted using
|
873 |
+
any of those formulas.
|
874 |
+
"""
|
875 |
+
# Try to find a known invertible matrix. Note that the Schur complement
|
876 |
+
# is currently not being considered for this
|
877 |
+
A_inv = ask(Q.invertible(A))
|
878 |
+
if A_inv == True:
|
879 |
+
return 'A'
|
880 |
+
B_inv = ask(Q.invertible(B))
|
881 |
+
if B_inv == True:
|
882 |
+
return 'B'
|
883 |
+
C_inv = ask(Q.invertible(C))
|
884 |
+
if C_inv == True:
|
885 |
+
return 'C'
|
886 |
+
D_inv = ask(Q.invertible(D))
|
887 |
+
if D_inv == True:
|
888 |
+
return 'D'
|
889 |
+
# Otherwise try to find a matrix that isn't known to be non-invertible
|
890 |
+
if A_inv != False:
|
891 |
+
return 'A'
|
892 |
+
if B_inv != False:
|
893 |
+
return 'B'
|
894 |
+
if C_inv != False:
|
895 |
+
return 'C'
|
896 |
+
if D_inv != False:
|
897 |
+
return 'D'
|
898 |
+
return None
|
899 |
+
|
900 |
+
|
901 |
+
def deblock(B):
|
902 |
+
""" Flatten a BlockMatrix of BlockMatrices """
|
903 |
+
if not isinstance(B, BlockMatrix) or not B.blocks.has(BlockMatrix):
|
904 |
+
return B
|
905 |
+
wrap = lambda x: x if isinstance(x, BlockMatrix) else BlockMatrix([[x]])
|
906 |
+
bb = B.blocks.applyfunc(wrap) # everything is a block
|
907 |
+
|
908 |
+
try:
|
909 |
+
MM = Matrix(0, sum(bb[0, i].blocks.shape[1] for i in range(bb.shape[1])), [])
|
910 |
+
for row in range(0, bb.shape[0]):
|
911 |
+
M = Matrix(bb[row, 0].blocks)
|
912 |
+
for col in range(1, bb.shape[1]):
|
913 |
+
M = M.row_join(bb[row, col].blocks)
|
914 |
+
MM = MM.col_join(M)
|
915 |
+
|
916 |
+
return BlockMatrix(MM)
|
917 |
+
except ShapeError:
|
918 |
+
return B
|
919 |
+
|
920 |
+
|
921 |
+
def reblock_2x2(expr):
|
922 |
+
"""
|
923 |
+
Reblock a BlockMatrix so that it has 2x2 blocks of block matrices. If
|
924 |
+
possible in such a way that the matrix continues to be invertible using the
|
925 |
+
classical 2x2 block inversion formulas.
|
926 |
+
"""
|
927 |
+
if not isinstance(expr, BlockMatrix) or not all(d > 2 for d in expr.blockshape):
|
928 |
+
return expr
|
929 |
+
|
930 |
+
BM = BlockMatrix # for brevity's sake
|
931 |
+
rowblocks, colblocks = expr.blockshape
|
932 |
+
blocks = expr.blocks
|
933 |
+
for i in range(1, rowblocks):
|
934 |
+
for j in range(1, colblocks):
|
935 |
+
# try to split rows at i and cols at j
|
936 |
+
A = bc_unpack(BM(blocks[:i, :j]))
|
937 |
+
B = bc_unpack(BM(blocks[:i, j:]))
|
938 |
+
C = bc_unpack(BM(blocks[i:, :j]))
|
939 |
+
D = bc_unpack(BM(blocks[i:, j:]))
|
940 |
+
|
941 |
+
formula = _choose_2x2_inversion_formula(A, B, C, D)
|
942 |
+
if formula is not None:
|
943 |
+
return BlockMatrix([[A, B], [C, D]])
|
944 |
+
|
945 |
+
# else: nothing worked, just split upper left corner
|
946 |
+
return BM([[blocks[0, 0], BM(blocks[0, 1:])],
|
947 |
+
[BM(blocks[1:, 0]), BM(blocks[1:, 1:])]])
|
948 |
+
|
949 |
+
|
950 |
+
def bounds(sizes):
|
951 |
+
""" Convert sequence of numbers into pairs of low-high pairs
|
952 |
+
|
953 |
+
>>> from sympy.matrices.expressions.blockmatrix import bounds
|
954 |
+
>>> bounds((1, 10, 50))
|
955 |
+
[(0, 1), (1, 11), (11, 61)]
|
956 |
+
"""
|
957 |
+
low = 0
|
958 |
+
rv = []
|
959 |
+
for size in sizes:
|
960 |
+
rv.append((low, low + size))
|
961 |
+
low += size
|
962 |
+
return rv
|
963 |
+
|
964 |
+
def blockcut(expr, rowsizes, colsizes):
|
965 |
+
""" Cut a matrix expression into Blocks
|
966 |
+
|
967 |
+
>>> from sympy import ImmutableMatrix, blockcut
|
968 |
+
>>> M = ImmutableMatrix(4, 4, range(16))
|
969 |
+
>>> B = blockcut(M, (1, 3), (1, 3))
|
970 |
+
>>> type(B).__name__
|
971 |
+
'BlockMatrix'
|
972 |
+
>>> ImmutableMatrix(B.blocks[0, 1])
|
973 |
+
Matrix([[1, 2, 3]])
|
974 |
+
"""
|
975 |
+
|
976 |
+
rowbounds = bounds(rowsizes)
|
977 |
+
colbounds = bounds(colsizes)
|
978 |
+
return BlockMatrix([[MatrixSlice(expr, rowbound, colbound)
|
979 |
+
for colbound in colbounds]
|
980 |
+
for rowbound in rowbounds])
|
.venv/lib/python3.11/site-packages/sympy/matrices/expressions/dotproduct.py
ADDED
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from sympy.core import Basic, Expr
|
2 |
+
from sympy.core.sympify import _sympify
|
3 |
+
from sympy.matrices.expressions.transpose import transpose
|
4 |
+
|
5 |
+
|
6 |
+
class DotProduct(Expr):
|
7 |
+
"""
|
8 |
+
Dot product of vector matrices
|
9 |
+
|
10 |
+
The input should be two 1 x n or n x 1 matrices. The output represents the
|
11 |
+
scalar dotproduct.
|
12 |
+
|
13 |
+
This is similar to using MatrixElement and MatMul, except DotProduct does
|
14 |
+
not require that one vector to be a row vector and the other vector to be
|
15 |
+
a column vector.
|
16 |
+
|
17 |
+
>>> from sympy import MatrixSymbol, DotProduct
|
18 |
+
>>> A = MatrixSymbol('A', 1, 3)
|
19 |
+
>>> B = MatrixSymbol('B', 1, 3)
|
20 |
+
>>> DotProduct(A, B)
|
21 |
+
DotProduct(A, B)
|
22 |
+
>>> DotProduct(A, B).doit()
|
23 |
+
A[0, 0]*B[0, 0] + A[0, 1]*B[0, 1] + A[0, 2]*B[0, 2]
|
24 |
+
"""
|
25 |
+
|
26 |
+
def __new__(cls, arg1, arg2):
|
27 |
+
arg1, arg2 = _sympify((arg1, arg2))
|
28 |
+
|
29 |
+
if not arg1.is_Matrix:
|
30 |
+
raise TypeError("Argument 1 of DotProduct is not a matrix")
|
31 |
+
if not arg2.is_Matrix:
|
32 |
+
raise TypeError("Argument 2 of DotProduct is not a matrix")
|
33 |
+
if not (1 in arg1.shape):
|
34 |
+
raise TypeError("Argument 1 of DotProduct is not a vector")
|
35 |
+
if not (1 in arg2.shape):
|
36 |
+
raise TypeError("Argument 2 of DotProduct is not a vector")
|
37 |
+
|
38 |
+
if set(arg1.shape) != set(arg2.shape):
|
39 |
+
raise TypeError("DotProduct arguments are not the same length")
|
40 |
+
|
41 |
+
return Basic.__new__(cls, arg1, arg2)
|
42 |
+
|
43 |
+
def doit(self, expand=False, **hints):
|
44 |
+
if self.args[0].shape == self.args[1].shape:
|
45 |
+
if self.args[0].shape[0] == 1:
|
46 |
+
mul = self.args[0]*transpose(self.args[1])
|
47 |
+
else:
|
48 |
+
mul = transpose(self.args[0])*self.args[1]
|
49 |
+
else:
|
50 |
+
if self.args[0].shape[0] == 1:
|
51 |
+
mul = self.args[0]*self.args[1]
|
52 |
+
else:
|
53 |
+
mul = transpose(self.args[0])*transpose(self.args[1])
|
54 |
+
|
55 |
+
return mul[0]
|
.venv/lib/python3.11/site-packages/sympy/matrices/expressions/factorizations.py
ADDED
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from sympy.matrices.expressions import MatrixExpr
|
2 |
+
from sympy.assumptions.ask import Q
|
3 |
+
|
4 |
+
class Factorization(MatrixExpr):
|
5 |
+
arg = property(lambda self: self.args[0])
|
6 |
+
shape = property(lambda self: self.arg.shape) # type: ignore
|
7 |
+
|
8 |
+
class LofLU(Factorization):
|
9 |
+
@property
|
10 |
+
def predicates(self):
|
11 |
+
return (Q.lower_triangular,)
|
12 |
+
class UofLU(Factorization):
|
13 |
+
@property
|
14 |
+
def predicates(self):
|
15 |
+
return (Q.upper_triangular,)
|
16 |
+
|
17 |
+
class LofCholesky(LofLU): pass
|
18 |
+
class UofCholesky(UofLU): pass
|
19 |
+
|
20 |
+
class QofQR(Factorization):
|
21 |
+
@property
|
22 |
+
def predicates(self):
|
23 |
+
return (Q.orthogonal,)
|
24 |
+
class RofQR(Factorization):
|
25 |
+
@property
|
26 |
+
def predicates(self):
|
27 |
+
return (Q.upper_triangular,)
|
28 |
+
|
29 |
+
class EigenVectors(Factorization):
|
30 |
+
@property
|
31 |
+
def predicates(self):
|
32 |
+
return (Q.orthogonal,)
|
33 |
+
class EigenValues(Factorization):
|
34 |
+
@property
|
35 |
+
def predicates(self):
|
36 |
+
return (Q.diagonal,)
|
37 |
+
|
38 |
+
class UofSVD(Factorization):
|
39 |
+
@property
|
40 |
+
def predicates(self):
|
41 |
+
return (Q.orthogonal,)
|
42 |
+
class SofSVD(Factorization):
|
43 |
+
@property
|
44 |
+
def predicates(self):
|
45 |
+
return (Q.diagonal,)
|
46 |
+
class VofSVD(Factorization):
|
47 |
+
@property
|
48 |
+
def predicates(self):
|
49 |
+
return (Q.orthogonal,)
|
50 |
+
|
51 |
+
|
52 |
+
def lu(expr):
|
53 |
+
return LofLU(expr), UofLU(expr)
|
54 |
+
|
55 |
+
def qr(expr):
|
56 |
+
return QofQR(expr), RofQR(expr)
|
57 |
+
|
58 |
+
def eig(expr):
|
59 |
+
return EigenValues(expr), EigenVectors(expr)
|
60 |
+
|
61 |
+
def svd(expr):
|
62 |
+
return UofSVD(expr), SofSVD(expr), VofSVD(expr)
|
.venv/lib/python3.11/site-packages/sympy/matrices/expressions/funcmatrix.py
ADDED
@@ -0,0 +1,118 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from .matexpr import MatrixExpr
|
2 |
+
from sympy.core.function import FunctionClass, Lambda
|
3 |
+
from sympy.core.symbol import Dummy
|
4 |
+
from sympy.core.sympify import _sympify, sympify
|
5 |
+
from sympy.matrices import Matrix
|
6 |
+
from sympy.functions.elementary.complexes import re, im
|
7 |
+
|
8 |
+
|
9 |
+
class FunctionMatrix(MatrixExpr):
|
10 |
+
"""Represents a matrix using a function (``Lambda``) which gives
|
11 |
+
outputs according to the coordinates of each matrix entries.
|
12 |
+
|
13 |
+
Parameters
|
14 |
+
==========
|
15 |
+
|
16 |
+
rows : nonnegative integer. Can be symbolic.
|
17 |
+
|
18 |
+
cols : nonnegative integer. Can be symbolic.
|
19 |
+
|
20 |
+
lamda : Function, Lambda or str
|
21 |
+
If it is a SymPy ``Function`` or ``Lambda`` instance,
|
22 |
+
it should be able to accept two arguments which represents the
|
23 |
+
matrix coordinates.
|
24 |
+
|
25 |
+
If it is a pure string containing Python ``lambda`` semantics,
|
26 |
+
it is interpreted by the SymPy parser and casted into a SymPy
|
27 |
+
``Lambda`` instance.
|
28 |
+
|
29 |
+
Examples
|
30 |
+
========
|
31 |
+
|
32 |
+
Creating a ``FunctionMatrix`` from ``Lambda``:
|
33 |
+
|
34 |
+
>>> from sympy import FunctionMatrix, symbols, Lambda, MatPow
|
35 |
+
>>> i, j, n, m = symbols('i,j,n,m')
|
36 |
+
>>> FunctionMatrix(n, m, Lambda((i, j), i + j))
|
37 |
+
FunctionMatrix(n, m, Lambda((i, j), i + j))
|
38 |
+
|
39 |
+
Creating a ``FunctionMatrix`` from a SymPy function:
|
40 |
+
|
41 |
+
>>> from sympy import KroneckerDelta
|
42 |
+
>>> X = FunctionMatrix(3, 3, KroneckerDelta)
|
43 |
+
>>> X.as_explicit()
|
44 |
+
Matrix([
|
45 |
+
[1, 0, 0],
|
46 |
+
[0, 1, 0],
|
47 |
+
[0, 0, 1]])
|
48 |
+
|
49 |
+
Creating a ``FunctionMatrix`` from a SymPy undefined function:
|
50 |
+
|
51 |
+
>>> from sympy import Function
|
52 |
+
>>> f = Function('f')
|
53 |
+
>>> X = FunctionMatrix(3, 3, f)
|
54 |
+
>>> X.as_explicit()
|
55 |
+
Matrix([
|
56 |
+
[f(0, 0), f(0, 1), f(0, 2)],
|
57 |
+
[f(1, 0), f(1, 1), f(1, 2)],
|
58 |
+
[f(2, 0), f(2, 1), f(2, 2)]])
|
59 |
+
|
60 |
+
Creating a ``FunctionMatrix`` from Python ``lambda``:
|
61 |
+
|
62 |
+
>>> FunctionMatrix(n, m, 'lambda i, j: i + j')
|
63 |
+
FunctionMatrix(n, m, Lambda((i, j), i + j))
|
64 |
+
|
65 |
+
Example of lazy evaluation of matrix product:
|
66 |
+
|
67 |
+
>>> Y = FunctionMatrix(1000, 1000, Lambda((i, j), i + j))
|
68 |
+
>>> isinstance(Y*Y, MatPow) # this is an expression object
|
69 |
+
True
|
70 |
+
>>> (Y**2)[10,10] # So this is evaluated lazily
|
71 |
+
342923500
|
72 |
+
|
73 |
+
Notes
|
74 |
+
=====
|
75 |
+
|
76 |
+
This class provides an alternative way to represent an extremely
|
77 |
+
dense matrix with entries in some form of a sequence, in a most
|
78 |
+
sparse way.
|
79 |
+
"""
|
80 |
+
def __new__(cls, rows, cols, lamda):
|
81 |
+
rows, cols = _sympify(rows), _sympify(cols)
|
82 |
+
cls._check_dim(rows)
|
83 |
+
cls._check_dim(cols)
|
84 |
+
|
85 |
+
lamda = sympify(lamda)
|
86 |
+
if not isinstance(lamda, (FunctionClass, Lambda)):
|
87 |
+
raise ValueError(
|
88 |
+
"{} should be compatible with SymPy function classes."
|
89 |
+
.format(lamda))
|
90 |
+
|
91 |
+
if 2 not in lamda.nargs:
|
92 |
+
raise ValueError(
|
93 |
+
'{} should be able to accept 2 arguments.'.format(lamda))
|
94 |
+
|
95 |
+
if not isinstance(lamda, Lambda):
|
96 |
+
i, j = Dummy('i'), Dummy('j')
|
97 |
+
lamda = Lambda((i, j), lamda(i, j))
|
98 |
+
|
99 |
+
return super().__new__(cls, rows, cols, lamda)
|
100 |
+
|
101 |
+
@property
|
102 |
+
def shape(self):
|
103 |
+
return self.args[0:2]
|
104 |
+
|
105 |
+
@property
|
106 |
+
def lamda(self):
|
107 |
+
return self.args[2]
|
108 |
+
|
109 |
+
def _entry(self, i, j, **kwargs):
|
110 |
+
return self.lamda(i, j)
|
111 |
+
|
112 |
+
def _eval_trace(self):
|
113 |
+
from sympy.matrices.expressions.trace import Trace
|
114 |
+
from sympy.concrete.summations import Sum
|
115 |
+
return Trace(self).rewrite(Sum).doit()
|
116 |
+
|
117 |
+
def _eval_as_real_imag(self):
|
118 |
+
return (re(Matrix(self)), im(Matrix(self)))
|
.venv/lib/python3.11/site-packages/sympy/matrices/expressions/inverse.py
ADDED
@@ -0,0 +1,112 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from sympy.core.sympify import _sympify
|
2 |
+
from sympy.core import S, Basic
|
3 |
+
|
4 |
+
from sympy.matrices.exceptions import NonSquareMatrixError
|
5 |
+
from sympy.matrices.expressions.matpow import MatPow
|
6 |
+
|
7 |
+
|
8 |
+
class Inverse(MatPow):
|
9 |
+
"""
|
10 |
+
The multiplicative inverse of a matrix expression
|
11 |
+
|
12 |
+
This is a symbolic object that simply stores its argument without
|
13 |
+
evaluating it. To actually compute the inverse, use the ``.inverse()``
|
14 |
+
method of matrices.
|
15 |
+
|
16 |
+
Examples
|
17 |
+
========
|
18 |
+
|
19 |
+
>>> from sympy import MatrixSymbol, Inverse
|
20 |
+
>>> A = MatrixSymbol('A', 3, 3)
|
21 |
+
>>> B = MatrixSymbol('B', 3, 3)
|
22 |
+
>>> Inverse(A)
|
23 |
+
A**(-1)
|
24 |
+
>>> A.inverse() == Inverse(A)
|
25 |
+
True
|
26 |
+
>>> (A*B).inverse()
|
27 |
+
B**(-1)*A**(-1)
|
28 |
+
>>> Inverse(A*B)
|
29 |
+
(A*B)**(-1)
|
30 |
+
|
31 |
+
"""
|
32 |
+
is_Inverse = True
|
33 |
+
exp = S.NegativeOne
|
34 |
+
|
35 |
+
def __new__(cls, mat, exp=S.NegativeOne):
|
36 |
+
# exp is there to make it consistent with
|
37 |
+
# inverse.func(*inverse.args) == inverse
|
38 |
+
mat = _sympify(mat)
|
39 |
+
exp = _sympify(exp)
|
40 |
+
if not mat.is_Matrix:
|
41 |
+
raise TypeError("mat should be a matrix")
|
42 |
+
if mat.is_square is False:
|
43 |
+
raise NonSquareMatrixError("Inverse of non-square matrix %s" % mat)
|
44 |
+
return Basic.__new__(cls, mat, exp)
|
45 |
+
|
46 |
+
@property
|
47 |
+
def arg(self):
|
48 |
+
return self.args[0]
|
49 |
+
|
50 |
+
@property
|
51 |
+
def shape(self):
|
52 |
+
return self.arg.shape
|
53 |
+
|
54 |
+
def _eval_inverse(self):
|
55 |
+
return self.arg
|
56 |
+
|
57 |
+
def _eval_transpose(self):
|
58 |
+
return Inverse(self.arg.transpose())
|
59 |
+
|
60 |
+
def _eval_adjoint(self):
|
61 |
+
return Inverse(self.arg.adjoint())
|
62 |
+
|
63 |
+
def _eval_conjugate(self):
|
64 |
+
return Inverse(self.arg.conjugate())
|
65 |
+
|
66 |
+
def _eval_determinant(self):
|
67 |
+
from sympy.matrices.expressions.determinant import det
|
68 |
+
return 1/det(self.arg)
|
69 |
+
|
70 |
+
def doit(self, **hints):
|
71 |
+
if 'inv_expand' in hints and hints['inv_expand'] == False:
|
72 |
+
return self
|
73 |
+
|
74 |
+
arg = self.arg
|
75 |
+
if hints.get('deep', True):
|
76 |
+
arg = arg.doit(**hints)
|
77 |
+
|
78 |
+
return arg.inverse()
|
79 |
+
|
80 |
+
def _eval_derivative_matrix_lines(self, x):
|
81 |
+
arg = self.args[0]
|
82 |
+
lines = arg._eval_derivative_matrix_lines(x)
|
83 |
+
for line in lines:
|
84 |
+
line.first_pointer *= -self.T
|
85 |
+
line.second_pointer *= self
|
86 |
+
return lines
|
87 |
+
|
88 |
+
|
89 |
+
from sympy.assumptions.ask import ask, Q
|
90 |
+
from sympy.assumptions.refine import handlers_dict
|
91 |
+
|
92 |
+
|
93 |
+
def refine_Inverse(expr, assumptions):
|
94 |
+
"""
|
95 |
+
>>> from sympy import MatrixSymbol, Q, assuming, refine
|
96 |
+
>>> X = MatrixSymbol('X', 2, 2)
|
97 |
+
>>> X.I
|
98 |
+
X**(-1)
|
99 |
+
>>> with assuming(Q.orthogonal(X)):
|
100 |
+
... print(refine(X.I))
|
101 |
+
X.T
|
102 |
+
"""
|
103 |
+
if ask(Q.orthogonal(expr), assumptions):
|
104 |
+
return expr.arg.T
|
105 |
+
elif ask(Q.unitary(expr), assumptions):
|
106 |
+
return expr.arg.conjugate()
|
107 |
+
elif ask(Q.singular(expr), assumptions):
|
108 |
+
raise ValueError("Inverse of singular matrix %s" % expr.arg)
|
109 |
+
|
110 |
+
return expr
|
111 |
+
|
112 |
+
handlers_dict['Inverse'] = refine_Inverse
|
.venv/lib/python3.11/site-packages/sympy/matrices/expressions/kronecker.py
ADDED
@@ -0,0 +1,434 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""Implementation of the Kronecker product"""
|
2 |
+
from functools import reduce
|
3 |
+
from math import prod
|
4 |
+
|
5 |
+
from sympy.core import Mul, sympify
|
6 |
+
from sympy.functions import adjoint
|
7 |
+
from sympy.matrices.exceptions import ShapeError
|
8 |
+
from sympy.matrices.expressions.matexpr import MatrixExpr
|
9 |
+
from sympy.matrices.expressions.transpose import transpose
|
10 |
+
from sympy.matrices.expressions.special import Identity
|
11 |
+
from sympy.matrices.matrixbase import MatrixBase
|
12 |
+
from sympy.strategies import (
|
13 |
+
canon, condition, distribute, do_one, exhaust, flatten, typed, unpack)
|
14 |
+
from sympy.strategies.traverse import bottom_up
|
15 |
+
from sympy.utilities import sift
|
16 |
+
|
17 |
+
from .matadd import MatAdd
|
18 |
+
from .matmul import MatMul
|
19 |
+
from .matpow import MatPow
|
20 |
+
|
21 |
+
|
22 |
+
def kronecker_product(*matrices):
|
23 |
+
"""
|
24 |
+
The Kronecker product of two or more arguments.
|
25 |
+
|
26 |
+
This computes the explicit Kronecker product for subclasses of
|
27 |
+
``MatrixBase`` i.e. explicit matrices. Otherwise, a symbolic
|
28 |
+
``KroneckerProduct`` object is returned.
|
29 |
+
|
30 |
+
|
31 |
+
Examples
|
32 |
+
========
|
33 |
+
|
34 |
+
For ``MatrixSymbol`` arguments a ``KroneckerProduct`` object is returned.
|
35 |
+
Elements of this matrix can be obtained by indexing, or for MatrixSymbols
|
36 |
+
with known dimension the explicit matrix can be obtained with
|
37 |
+
``.as_explicit()``
|
38 |
+
|
39 |
+
>>> from sympy import kronecker_product, MatrixSymbol
|
40 |
+
>>> A = MatrixSymbol('A', 2, 2)
|
41 |
+
>>> B = MatrixSymbol('B', 2, 2)
|
42 |
+
>>> kronecker_product(A)
|
43 |
+
A
|
44 |
+
>>> kronecker_product(A, B)
|
45 |
+
KroneckerProduct(A, B)
|
46 |
+
>>> kronecker_product(A, B)[0, 1]
|
47 |
+
A[0, 0]*B[0, 1]
|
48 |
+
>>> kronecker_product(A, B).as_explicit()
|
49 |
+
Matrix([
|
50 |
+
[A[0, 0]*B[0, 0], A[0, 0]*B[0, 1], A[0, 1]*B[0, 0], A[0, 1]*B[0, 1]],
|
51 |
+
[A[0, 0]*B[1, 0], A[0, 0]*B[1, 1], A[0, 1]*B[1, 0], A[0, 1]*B[1, 1]],
|
52 |
+
[A[1, 0]*B[0, 0], A[1, 0]*B[0, 1], A[1, 1]*B[0, 0], A[1, 1]*B[0, 1]],
|
53 |
+
[A[1, 0]*B[1, 0], A[1, 0]*B[1, 1], A[1, 1]*B[1, 0], A[1, 1]*B[1, 1]]])
|
54 |
+
|
55 |
+
For explicit matrices the Kronecker product is returned as a Matrix
|
56 |
+
|
57 |
+
>>> from sympy import Matrix, kronecker_product
|
58 |
+
>>> sigma_x = Matrix([
|
59 |
+
... [0, 1],
|
60 |
+
... [1, 0]])
|
61 |
+
...
|
62 |
+
>>> Isigma_y = Matrix([
|
63 |
+
... [0, 1],
|
64 |
+
... [-1, 0]])
|
65 |
+
...
|
66 |
+
>>> kronecker_product(sigma_x, Isigma_y)
|
67 |
+
Matrix([
|
68 |
+
[ 0, 0, 0, 1],
|
69 |
+
[ 0, 0, -1, 0],
|
70 |
+
[ 0, 1, 0, 0],
|
71 |
+
[-1, 0, 0, 0]])
|
72 |
+
|
73 |
+
See Also
|
74 |
+
========
|
75 |
+
KroneckerProduct
|
76 |
+
|
77 |
+
"""
|
78 |
+
if not matrices:
|
79 |
+
raise TypeError("Empty Kronecker product is undefined")
|
80 |
+
if len(matrices) == 1:
|
81 |
+
return matrices[0]
|
82 |
+
else:
|
83 |
+
return KroneckerProduct(*matrices).doit()
|
84 |
+
|
85 |
+
|
86 |
+
class KroneckerProduct(MatrixExpr):
|
87 |
+
"""
|
88 |
+
The Kronecker product of two or more arguments.
|
89 |
+
|
90 |
+
The Kronecker product is a non-commutative product of matrices.
|
91 |
+
Given two matrices of dimension (m, n) and (s, t) it produces a matrix
|
92 |
+
of dimension (m s, n t).
|
93 |
+
|
94 |
+
This is a symbolic object that simply stores its argument without
|
95 |
+
evaluating it. To actually compute the product, use the function
|
96 |
+
``kronecker_product()`` or call the ``.doit()`` or ``.as_explicit()``
|
97 |
+
methods.
|
98 |
+
|
99 |
+
>>> from sympy import KroneckerProduct, MatrixSymbol
|
100 |
+
>>> A = MatrixSymbol('A', 5, 5)
|
101 |
+
>>> B = MatrixSymbol('B', 5, 5)
|
102 |
+
>>> isinstance(KroneckerProduct(A, B), KroneckerProduct)
|
103 |
+
True
|
104 |
+
"""
|
105 |
+
is_KroneckerProduct = True
|
106 |
+
|
107 |
+
def __new__(cls, *args, check=True):
|
108 |
+
args = list(map(sympify, args))
|
109 |
+
if all(a.is_Identity for a in args):
|
110 |
+
ret = Identity(prod(a.rows for a in args))
|
111 |
+
if all(isinstance(a, MatrixBase) for a in args):
|
112 |
+
return ret.as_explicit()
|
113 |
+
else:
|
114 |
+
return ret
|
115 |
+
|
116 |
+
if check:
|
117 |
+
validate(*args)
|
118 |
+
return super().__new__(cls, *args)
|
119 |
+
|
120 |
+
@property
|
121 |
+
def shape(self):
|
122 |
+
rows, cols = self.args[0].shape
|
123 |
+
for mat in self.args[1:]:
|
124 |
+
rows *= mat.rows
|
125 |
+
cols *= mat.cols
|
126 |
+
return (rows, cols)
|
127 |
+
|
128 |
+
def _entry(self, i, j, **kwargs):
|
129 |
+
result = 1
|
130 |
+
for mat in reversed(self.args):
|
131 |
+
i, m = divmod(i, mat.rows)
|
132 |
+
j, n = divmod(j, mat.cols)
|
133 |
+
result *= mat[m, n]
|
134 |
+
return result
|
135 |
+
|
136 |
+
def _eval_adjoint(self):
|
137 |
+
return KroneckerProduct(*list(map(adjoint, self.args))).doit()
|
138 |
+
|
139 |
+
def _eval_conjugate(self):
|
140 |
+
return KroneckerProduct(*[a.conjugate() for a in self.args]).doit()
|
141 |
+
|
142 |
+
def _eval_transpose(self):
|
143 |
+
return KroneckerProduct(*list(map(transpose, self.args))).doit()
|
144 |
+
|
145 |
+
def _eval_trace(self):
|
146 |
+
from .trace import trace
|
147 |
+
return Mul(*[trace(a) for a in self.args])
|
148 |
+
|
149 |
+
def _eval_determinant(self):
|
150 |
+
from .determinant import det, Determinant
|
151 |
+
if not all(a.is_square for a in self.args):
|
152 |
+
return Determinant(self)
|
153 |
+
|
154 |
+
m = self.rows
|
155 |
+
return Mul(*[det(a)**(m/a.rows) for a in self.args])
|
156 |
+
|
157 |
+
def _eval_inverse(self):
|
158 |
+
try:
|
159 |
+
return KroneckerProduct(*[a.inverse() for a in self.args])
|
160 |
+
except ShapeError:
|
161 |
+
from sympy.matrices.expressions.inverse import Inverse
|
162 |
+
return Inverse(self)
|
163 |
+
|
164 |
+
def structurally_equal(self, other):
|
165 |
+
'''Determine whether two matrices have the same Kronecker product structure
|
166 |
+
|
167 |
+
Examples
|
168 |
+
========
|
169 |
+
|
170 |
+
>>> from sympy import KroneckerProduct, MatrixSymbol, symbols
|
171 |
+
>>> m, n = symbols(r'm, n', integer=True)
|
172 |
+
>>> A = MatrixSymbol('A', m, m)
|
173 |
+
>>> B = MatrixSymbol('B', n, n)
|
174 |
+
>>> C = MatrixSymbol('C', m, m)
|
175 |
+
>>> D = MatrixSymbol('D', n, n)
|
176 |
+
>>> KroneckerProduct(A, B).structurally_equal(KroneckerProduct(C, D))
|
177 |
+
True
|
178 |
+
>>> KroneckerProduct(A, B).structurally_equal(KroneckerProduct(D, C))
|
179 |
+
False
|
180 |
+
>>> KroneckerProduct(A, B).structurally_equal(C)
|
181 |
+
False
|
182 |
+
'''
|
183 |
+
# Inspired by BlockMatrix
|
184 |
+
return (isinstance(other, KroneckerProduct)
|
185 |
+
and self.shape == other.shape
|
186 |
+
and len(self.args) == len(other.args)
|
187 |
+
and all(a.shape == b.shape for (a, b) in zip(self.args, other.args)))
|
188 |
+
|
189 |
+
def has_matching_shape(self, other):
|
190 |
+
'''Determine whether two matrices have the appropriate structure to bring matrix
|
191 |
+
multiplication inside the KroneckerProdut
|
192 |
+
|
193 |
+
Examples
|
194 |
+
========
|
195 |
+
>>> from sympy import KroneckerProduct, MatrixSymbol, symbols
|
196 |
+
>>> m, n = symbols(r'm, n', integer=True)
|
197 |
+
>>> A = MatrixSymbol('A', m, n)
|
198 |
+
>>> B = MatrixSymbol('B', n, m)
|
199 |
+
>>> KroneckerProduct(A, B).has_matching_shape(KroneckerProduct(B, A))
|
200 |
+
True
|
201 |
+
>>> KroneckerProduct(A, B).has_matching_shape(KroneckerProduct(A, B))
|
202 |
+
False
|
203 |
+
>>> KroneckerProduct(A, B).has_matching_shape(A)
|
204 |
+
False
|
205 |
+
'''
|
206 |
+
return (isinstance(other, KroneckerProduct)
|
207 |
+
and self.cols == other.rows
|
208 |
+
and len(self.args) == len(other.args)
|
209 |
+
and all(a.cols == b.rows for (a, b) in zip(self.args, other.args)))
|
210 |
+
|
211 |
+
def _eval_expand_kroneckerproduct(self, **hints):
|
212 |
+
return flatten(canon(typed({KroneckerProduct: distribute(KroneckerProduct, MatAdd)}))(self))
|
213 |
+
|
214 |
+
def _kronecker_add(self, other):
|
215 |
+
if self.structurally_equal(other):
|
216 |
+
return self.__class__(*[a + b for (a, b) in zip(self.args, other.args)])
|
217 |
+
else:
|
218 |
+
return self + other
|
219 |
+
|
220 |
+
def _kronecker_mul(self, other):
|
221 |
+
if self.has_matching_shape(other):
|
222 |
+
return self.__class__(*[a*b for (a, b) in zip(self.args, other.args)])
|
223 |
+
else:
|
224 |
+
return self * other
|
225 |
+
|
226 |
+
def doit(self, **hints):
|
227 |
+
deep = hints.get('deep', True)
|
228 |
+
if deep:
|
229 |
+
args = [arg.doit(**hints) for arg in self.args]
|
230 |
+
else:
|
231 |
+
args = self.args
|
232 |
+
return canonicalize(KroneckerProduct(*args))
|
233 |
+
|
234 |
+
|
235 |
+
def validate(*args):
|
236 |
+
if not all(arg.is_Matrix for arg in args):
|
237 |
+
raise TypeError("Mix of Matrix and Scalar symbols")
|
238 |
+
|
239 |
+
|
240 |
+
# rules
|
241 |
+
|
242 |
+
def extract_commutative(kron):
|
243 |
+
c_part = []
|
244 |
+
nc_part = []
|
245 |
+
for arg in kron.args:
|
246 |
+
c, nc = arg.args_cnc()
|
247 |
+
c_part.extend(c)
|
248 |
+
nc_part.append(Mul._from_args(nc))
|
249 |
+
|
250 |
+
c_part = Mul(*c_part)
|
251 |
+
if c_part != 1:
|
252 |
+
return c_part*KroneckerProduct(*nc_part)
|
253 |
+
return kron
|
254 |
+
|
255 |
+
|
256 |
+
def matrix_kronecker_product(*matrices):
|
257 |
+
"""Compute the Kronecker product of a sequence of SymPy Matrices.
|
258 |
+
|
259 |
+
This is the standard Kronecker product of matrices [1].
|
260 |
+
|
261 |
+
Parameters
|
262 |
+
==========
|
263 |
+
|
264 |
+
matrices : tuple of MatrixBase instances
|
265 |
+
The matrices to take the Kronecker product of.
|
266 |
+
|
267 |
+
Returns
|
268 |
+
=======
|
269 |
+
|
270 |
+
matrix : MatrixBase
|
271 |
+
The Kronecker product matrix.
|
272 |
+
|
273 |
+
Examples
|
274 |
+
========
|
275 |
+
|
276 |
+
>>> from sympy import Matrix
|
277 |
+
>>> from sympy.matrices.expressions.kronecker import (
|
278 |
+
... matrix_kronecker_product)
|
279 |
+
|
280 |
+
>>> m1 = Matrix([[1,2],[3,4]])
|
281 |
+
>>> m2 = Matrix([[1,0],[0,1]])
|
282 |
+
>>> matrix_kronecker_product(m1, m2)
|
283 |
+
Matrix([
|
284 |
+
[1, 0, 2, 0],
|
285 |
+
[0, 1, 0, 2],
|
286 |
+
[3, 0, 4, 0],
|
287 |
+
[0, 3, 0, 4]])
|
288 |
+
>>> matrix_kronecker_product(m2, m1)
|
289 |
+
Matrix([
|
290 |
+
[1, 2, 0, 0],
|
291 |
+
[3, 4, 0, 0],
|
292 |
+
[0, 0, 1, 2],
|
293 |
+
[0, 0, 3, 4]])
|
294 |
+
|
295 |
+
References
|
296 |
+
==========
|
297 |
+
|
298 |
+
.. [1] https://en.wikipedia.org/wiki/Kronecker_product
|
299 |
+
"""
|
300 |
+
# Make sure we have a sequence of Matrices
|
301 |
+
if not all(isinstance(m, MatrixBase) for m in matrices):
|
302 |
+
raise TypeError(
|
303 |
+
'Sequence of Matrices expected, got: %s' % repr(matrices)
|
304 |
+
)
|
305 |
+
|
306 |
+
# Pull out the first element in the product.
|
307 |
+
matrix_expansion = matrices[-1]
|
308 |
+
# Do the kronecker product working from right to left.
|
309 |
+
for mat in reversed(matrices[:-1]):
|
310 |
+
rows = mat.rows
|
311 |
+
cols = mat.cols
|
312 |
+
# Go through each row appending kronecker product to.
|
313 |
+
# running matrix_expansion.
|
314 |
+
for i in range(rows):
|
315 |
+
start = matrix_expansion*mat[i*cols]
|
316 |
+
# Go through each column joining each item
|
317 |
+
for j in range(cols - 1):
|
318 |
+
start = start.row_join(
|
319 |
+
matrix_expansion*mat[i*cols + j + 1]
|
320 |
+
)
|
321 |
+
# If this is the first element, make it the start of the
|
322 |
+
# new row.
|
323 |
+
if i == 0:
|
324 |
+
next = start
|
325 |
+
else:
|
326 |
+
next = next.col_join(start)
|
327 |
+
matrix_expansion = next
|
328 |
+
|
329 |
+
MatrixClass = max(matrices, key=lambda M: M._class_priority).__class__
|
330 |
+
if isinstance(matrix_expansion, MatrixClass):
|
331 |
+
return matrix_expansion
|
332 |
+
else:
|
333 |
+
return MatrixClass(matrix_expansion)
|
334 |
+
|
335 |
+
|
336 |
+
def explicit_kronecker_product(kron):
|
337 |
+
# Make sure we have a sequence of Matrices
|
338 |
+
if not all(isinstance(m, MatrixBase) for m in kron.args):
|
339 |
+
return kron
|
340 |
+
|
341 |
+
return matrix_kronecker_product(*kron.args)
|
342 |
+
|
343 |
+
|
344 |
+
rules = (unpack,
|
345 |
+
explicit_kronecker_product,
|
346 |
+
flatten,
|
347 |
+
extract_commutative)
|
348 |
+
|
349 |
+
canonicalize = exhaust(condition(lambda x: isinstance(x, KroneckerProduct),
|
350 |
+
do_one(*rules)))
|
351 |
+
|
352 |
+
|
353 |
+
def _kronecker_dims_key(expr):
|
354 |
+
if isinstance(expr, KroneckerProduct):
|
355 |
+
return tuple(a.shape for a in expr.args)
|
356 |
+
else:
|
357 |
+
return (0,)
|
358 |
+
|
359 |
+
|
360 |
+
def kronecker_mat_add(expr):
|
361 |
+
args = sift(expr.args, _kronecker_dims_key)
|
362 |
+
nonkrons = args.pop((0,), None)
|
363 |
+
if not args:
|
364 |
+
return expr
|
365 |
+
|
366 |
+
krons = [reduce(lambda x, y: x._kronecker_add(y), group)
|
367 |
+
for group in args.values()]
|
368 |
+
|
369 |
+
if not nonkrons:
|
370 |
+
return MatAdd(*krons)
|
371 |
+
else:
|
372 |
+
return MatAdd(*krons) + nonkrons
|
373 |
+
|
374 |
+
|
375 |
+
def kronecker_mat_mul(expr):
|
376 |
+
# modified from block matrix code
|
377 |
+
factor, matrices = expr.as_coeff_matrices()
|
378 |
+
|
379 |
+
i = 0
|
380 |
+
while i < len(matrices) - 1:
|
381 |
+
A, B = matrices[i:i+2]
|
382 |
+
if isinstance(A, KroneckerProduct) and isinstance(B, KroneckerProduct):
|
383 |
+
matrices[i] = A._kronecker_mul(B)
|
384 |
+
matrices.pop(i+1)
|
385 |
+
else:
|
386 |
+
i += 1
|
387 |
+
|
388 |
+
return factor*MatMul(*matrices)
|
389 |
+
|
390 |
+
|
391 |
+
def kronecker_mat_pow(expr):
|
392 |
+
if isinstance(expr.base, KroneckerProduct) and all(a.is_square for a in expr.base.args):
|
393 |
+
return KroneckerProduct(*[MatPow(a, expr.exp) for a in expr.base.args])
|
394 |
+
else:
|
395 |
+
return expr
|
396 |
+
|
397 |
+
|
398 |
+
def combine_kronecker(expr):
|
399 |
+
"""Combine KronekeckerProduct with expression.
|
400 |
+
|
401 |
+
If possible write operations on KroneckerProducts of compatible shapes
|
402 |
+
as a single KroneckerProduct.
|
403 |
+
|
404 |
+
Examples
|
405 |
+
========
|
406 |
+
|
407 |
+
>>> from sympy.matrices.expressions import combine_kronecker
|
408 |
+
>>> from sympy import MatrixSymbol, KroneckerProduct, symbols
|
409 |
+
>>> m, n = symbols(r'm, n', integer=True)
|
410 |
+
>>> A = MatrixSymbol('A', m, n)
|
411 |
+
>>> B = MatrixSymbol('B', n, m)
|
412 |
+
>>> combine_kronecker(KroneckerProduct(A, B)*KroneckerProduct(B, A))
|
413 |
+
KroneckerProduct(A*B, B*A)
|
414 |
+
>>> combine_kronecker(KroneckerProduct(A, B)+KroneckerProduct(B.T, A.T))
|
415 |
+
KroneckerProduct(A + B.T, B + A.T)
|
416 |
+
>>> C = MatrixSymbol('C', n, n)
|
417 |
+
>>> D = MatrixSymbol('D', m, m)
|
418 |
+
>>> combine_kronecker(KroneckerProduct(C, D)**m)
|
419 |
+
KroneckerProduct(C**m, D**m)
|
420 |
+
"""
|
421 |
+
def haskron(expr):
|
422 |
+
return isinstance(expr, MatrixExpr) and expr.has(KroneckerProduct)
|
423 |
+
|
424 |
+
rule = exhaust(
|
425 |
+
bottom_up(exhaust(condition(haskron, typed(
|
426 |
+
{MatAdd: kronecker_mat_add,
|
427 |
+
MatMul: kronecker_mat_mul,
|
428 |
+
MatPow: kronecker_mat_pow})))))
|
429 |
+
result = rule(expr)
|
430 |
+
doit = getattr(result, 'doit', None)
|
431 |
+
if doit is not None:
|
432 |
+
return doit()
|
433 |
+
else:
|
434 |
+
return result
|
.venv/lib/python3.11/site-packages/sympy/matrices/expressions/matexpr.py
ADDED
@@ -0,0 +1,888 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from __future__ import annotations
|
2 |
+
from functools import wraps
|
3 |
+
|
4 |
+
from sympy.core import S, Integer, Basic, Mul, Add
|
5 |
+
from sympy.core.assumptions import check_assumptions
|
6 |
+
from sympy.core.decorators import call_highest_priority
|
7 |
+
from sympy.core.expr import Expr, ExprBuilder
|
8 |
+
from sympy.core.logic import FuzzyBool
|
9 |
+
from sympy.core.symbol import Str, Dummy, symbols, Symbol
|
10 |
+
from sympy.core.sympify import SympifyError, _sympify
|
11 |
+
from sympy.external.gmpy import SYMPY_INTS
|
12 |
+
from sympy.functions import conjugate, adjoint
|
13 |
+
from sympy.functions.special.tensor_functions import KroneckerDelta
|
14 |
+
from sympy.matrices.exceptions import NonSquareMatrixError
|
15 |
+
from sympy.matrices.kind import MatrixKind
|
16 |
+
from sympy.matrices.matrixbase import MatrixBase
|
17 |
+
from sympy.multipledispatch import dispatch
|
18 |
+
from sympy.utilities.misc import filldedent
|
19 |
+
|
20 |
+
|
21 |
+
def _sympifyit(arg, retval=None):
|
22 |
+
# This version of _sympifyit sympifies MutableMatrix objects
|
23 |
+
def deco(func):
|
24 |
+
@wraps(func)
|
25 |
+
def __sympifyit_wrapper(a, b):
|
26 |
+
try:
|
27 |
+
b = _sympify(b)
|
28 |
+
return func(a, b)
|
29 |
+
except SympifyError:
|
30 |
+
return retval
|
31 |
+
|
32 |
+
return __sympifyit_wrapper
|
33 |
+
|
34 |
+
return deco
|
35 |
+
|
36 |
+
|
37 |
+
class MatrixExpr(Expr):
|
38 |
+
"""Superclass for Matrix Expressions
|
39 |
+
|
40 |
+
MatrixExprs represent abstract matrices, linear transformations represented
|
41 |
+
within a particular basis.
|
42 |
+
|
43 |
+
Examples
|
44 |
+
========
|
45 |
+
|
46 |
+
>>> from sympy import MatrixSymbol
|
47 |
+
>>> A = MatrixSymbol('A', 3, 3)
|
48 |
+
>>> y = MatrixSymbol('y', 3, 1)
|
49 |
+
>>> x = (A.T*A).I * A * y
|
50 |
+
|
51 |
+
See Also
|
52 |
+
========
|
53 |
+
|
54 |
+
MatrixSymbol, MatAdd, MatMul, Transpose, Inverse
|
55 |
+
"""
|
56 |
+
__slots__: tuple[str, ...] = ()
|
57 |
+
|
58 |
+
# Should not be considered iterable by the
|
59 |
+
# sympy.utilities.iterables.iterable function. Subclass that actually are
|
60 |
+
# iterable (i.e., explicit matrices) should set this to True.
|
61 |
+
_iterable = False
|
62 |
+
|
63 |
+
_op_priority = 11.0
|
64 |
+
|
65 |
+
is_Matrix: bool = True
|
66 |
+
is_MatrixExpr: bool = True
|
67 |
+
is_Identity: FuzzyBool = None
|
68 |
+
is_Inverse = False
|
69 |
+
is_Transpose = False
|
70 |
+
is_ZeroMatrix = False
|
71 |
+
is_MatAdd = False
|
72 |
+
is_MatMul = False
|
73 |
+
|
74 |
+
is_commutative = False
|
75 |
+
is_number = False
|
76 |
+
is_symbol = False
|
77 |
+
is_scalar = False
|
78 |
+
|
79 |
+
kind: MatrixKind = MatrixKind()
|
80 |
+
|
81 |
+
def __new__(cls, *args, **kwargs):
|
82 |
+
args = map(_sympify, args)
|
83 |
+
return Basic.__new__(cls, *args, **kwargs)
|
84 |
+
|
85 |
+
# The following is adapted from the core Expr object
|
86 |
+
|
87 |
+
@property
|
88 |
+
def shape(self) -> tuple[Expr, Expr]:
|
89 |
+
raise NotImplementedError
|
90 |
+
|
91 |
+
@property
|
92 |
+
def _add_handler(self):
|
93 |
+
return MatAdd
|
94 |
+
|
95 |
+
@property
|
96 |
+
def _mul_handler(self):
|
97 |
+
return MatMul
|
98 |
+
|
99 |
+
def __neg__(self):
|
100 |
+
return MatMul(S.NegativeOne, self).doit()
|
101 |
+
|
102 |
+
def __abs__(self):
|
103 |
+
raise NotImplementedError
|
104 |
+
|
105 |
+
@_sympifyit('other', NotImplemented)
|
106 |
+
@call_highest_priority('__radd__')
|
107 |
+
def __add__(self, other):
|
108 |
+
return MatAdd(self, other).doit()
|
109 |
+
|
110 |
+
@_sympifyit('other', NotImplemented)
|
111 |
+
@call_highest_priority('__add__')
|
112 |
+
def __radd__(self, other):
|
113 |
+
return MatAdd(other, self).doit()
|
114 |
+
|
115 |
+
@_sympifyit('other', NotImplemented)
|
116 |
+
@call_highest_priority('__rsub__')
|
117 |
+
def __sub__(self, other):
|
118 |
+
return MatAdd(self, -other).doit()
|
119 |
+
|
120 |
+
@_sympifyit('other', NotImplemented)
|
121 |
+
@call_highest_priority('__sub__')
|
122 |
+
def __rsub__(self, other):
|
123 |
+
return MatAdd(other, -self).doit()
|
124 |
+
|
125 |
+
@_sympifyit('other', NotImplemented)
|
126 |
+
@call_highest_priority('__rmul__')
|
127 |
+
def __mul__(self, other):
|
128 |
+
return MatMul(self, other).doit()
|
129 |
+
|
130 |
+
@_sympifyit('other', NotImplemented)
|
131 |
+
@call_highest_priority('__rmul__')
|
132 |
+
def __matmul__(self, other):
|
133 |
+
return MatMul(self, other).doit()
|
134 |
+
|
135 |
+
@_sympifyit('other', NotImplemented)
|
136 |
+
@call_highest_priority('__mul__')
|
137 |
+
def __rmul__(self, other):
|
138 |
+
return MatMul(other, self).doit()
|
139 |
+
|
140 |
+
@_sympifyit('other', NotImplemented)
|
141 |
+
@call_highest_priority('__mul__')
|
142 |
+
def __rmatmul__(self, other):
|
143 |
+
return MatMul(other, self).doit()
|
144 |
+
|
145 |
+
@_sympifyit('other', NotImplemented)
|
146 |
+
@call_highest_priority('__rpow__')
|
147 |
+
def __pow__(self, other):
|
148 |
+
return MatPow(self, other).doit()
|
149 |
+
|
150 |
+
@_sympifyit('other', NotImplemented)
|
151 |
+
@call_highest_priority('__pow__')
|
152 |
+
def __rpow__(self, other):
|
153 |
+
raise NotImplementedError("Matrix Power not defined")
|
154 |
+
|
155 |
+
@_sympifyit('other', NotImplemented)
|
156 |
+
@call_highest_priority('__rtruediv__')
|
157 |
+
def __truediv__(self, other):
|
158 |
+
return self * other**S.NegativeOne
|
159 |
+
|
160 |
+
@_sympifyit('other', NotImplemented)
|
161 |
+
@call_highest_priority('__truediv__')
|
162 |
+
def __rtruediv__(self, other):
|
163 |
+
raise NotImplementedError()
|
164 |
+
#return MatMul(other, Pow(self, S.NegativeOne))
|
165 |
+
|
166 |
+
@property
|
167 |
+
def rows(self):
|
168 |
+
return self.shape[0]
|
169 |
+
|
170 |
+
@property
|
171 |
+
def cols(self):
|
172 |
+
return self.shape[1]
|
173 |
+
|
174 |
+
@property
|
175 |
+
def is_square(self) -> bool | None:
|
176 |
+
rows, cols = self.shape
|
177 |
+
if isinstance(rows, Integer) and isinstance(cols, Integer):
|
178 |
+
return rows == cols
|
179 |
+
if rows == cols:
|
180 |
+
return True
|
181 |
+
return None
|
182 |
+
|
183 |
+
def _eval_conjugate(self):
|
184 |
+
from sympy.matrices.expressions.adjoint import Adjoint
|
185 |
+
return Adjoint(Transpose(self))
|
186 |
+
|
187 |
+
def as_real_imag(self, deep=True, **hints):
|
188 |
+
return self._eval_as_real_imag()
|
189 |
+
|
190 |
+
def _eval_as_real_imag(self):
|
191 |
+
real = S.Half * (self + self._eval_conjugate())
|
192 |
+
im = (self - self._eval_conjugate())/(2*S.ImaginaryUnit)
|
193 |
+
return (real, im)
|
194 |
+
|
195 |
+
def _eval_inverse(self):
|
196 |
+
return Inverse(self)
|
197 |
+
|
198 |
+
def _eval_determinant(self):
|
199 |
+
return Determinant(self)
|
200 |
+
|
201 |
+
def _eval_transpose(self):
|
202 |
+
return Transpose(self)
|
203 |
+
|
204 |
+
def _eval_trace(self):
|
205 |
+
return None
|
206 |
+
|
207 |
+
def _eval_power(self, exp):
|
208 |
+
"""
|
209 |
+
Override this in sub-classes to implement simplification of powers. The cases where the exponent
|
210 |
+
is -1, 0, 1 are already covered in MatPow.doit(), so implementations can exclude these cases.
|
211 |
+
"""
|
212 |
+
return MatPow(self, exp)
|
213 |
+
|
214 |
+
def _eval_simplify(self, **kwargs):
|
215 |
+
if self.is_Atom:
|
216 |
+
return self
|
217 |
+
else:
|
218 |
+
from sympy.simplify import simplify
|
219 |
+
return self.func(*[simplify(x, **kwargs) for x in self.args])
|
220 |
+
|
221 |
+
def _eval_adjoint(self):
|
222 |
+
from sympy.matrices.expressions.adjoint import Adjoint
|
223 |
+
return Adjoint(self)
|
224 |
+
|
225 |
+
def _eval_derivative_n_times(self, x, n):
|
226 |
+
return Basic._eval_derivative_n_times(self, x, n)
|
227 |
+
|
228 |
+
def _eval_derivative(self, x):
|
229 |
+
# `x` is a scalar:
|
230 |
+
if self.has(x):
|
231 |
+
# See if there are other methods using it:
|
232 |
+
return super()._eval_derivative(x)
|
233 |
+
else:
|
234 |
+
return ZeroMatrix(*self.shape)
|
235 |
+
|
236 |
+
@classmethod
|
237 |
+
def _check_dim(cls, dim):
|
238 |
+
"""Helper function to check invalid matrix dimensions"""
|
239 |
+
ok = not dim.is_Float and check_assumptions(
|
240 |
+
dim, integer=True, nonnegative=True)
|
241 |
+
if ok is False:
|
242 |
+
raise ValueError(
|
243 |
+
"The dimension specification {} should be "
|
244 |
+
"a nonnegative integer.".format(dim))
|
245 |
+
|
246 |
+
|
247 |
+
def _entry(self, i, j, **kwargs):
|
248 |
+
raise NotImplementedError(
|
249 |
+
"Indexing not implemented for %s" % self.__class__.__name__)
|
250 |
+
|
251 |
+
def adjoint(self):
|
252 |
+
return adjoint(self)
|
253 |
+
|
254 |
+
def as_coeff_Mul(self, rational=False):
|
255 |
+
"""Efficiently extract the coefficient of a product."""
|
256 |
+
return S.One, self
|
257 |
+
|
258 |
+
def conjugate(self):
|
259 |
+
return conjugate(self)
|
260 |
+
|
261 |
+
def transpose(self):
|
262 |
+
from sympy.matrices.expressions.transpose import transpose
|
263 |
+
return transpose(self)
|
264 |
+
|
265 |
+
@property
|
266 |
+
def T(self):
|
267 |
+
'''Matrix transposition'''
|
268 |
+
return self.transpose()
|
269 |
+
|
270 |
+
def inverse(self):
|
271 |
+
if self.is_square is False:
|
272 |
+
raise NonSquareMatrixError('Inverse of non-square matrix')
|
273 |
+
return self._eval_inverse()
|
274 |
+
|
275 |
+
def inv(self):
|
276 |
+
return self.inverse()
|
277 |
+
|
278 |
+
def det(self):
|
279 |
+
from sympy.matrices.expressions.determinant import det
|
280 |
+
return det(self)
|
281 |
+
|
282 |
+
@property
|
283 |
+
def I(self):
|
284 |
+
return self.inverse()
|
285 |
+
|
286 |
+
def valid_index(self, i, j):
|
287 |
+
def is_valid(idx):
|
288 |
+
return isinstance(idx, (int, Integer, Symbol, Expr))
|
289 |
+
return (is_valid(i) and is_valid(j) and
|
290 |
+
(self.rows is None or
|
291 |
+
(i >= -self.rows) != False and (i < self.rows) != False) and
|
292 |
+
(j >= -self.cols) != False and (j < self.cols) != False)
|
293 |
+
|
294 |
+
def __getitem__(self, key):
|
295 |
+
if not isinstance(key, tuple) and isinstance(key, slice):
|
296 |
+
from sympy.matrices.expressions.slice import MatrixSlice
|
297 |
+
return MatrixSlice(self, key, (0, None, 1))
|
298 |
+
if isinstance(key, tuple) and len(key) == 2:
|
299 |
+
i, j = key
|
300 |
+
if isinstance(i, slice) or isinstance(j, slice):
|
301 |
+
from sympy.matrices.expressions.slice import MatrixSlice
|
302 |
+
return MatrixSlice(self, i, j)
|
303 |
+
i, j = _sympify(i), _sympify(j)
|
304 |
+
if self.valid_index(i, j) != False:
|
305 |
+
return self._entry(i, j)
|
306 |
+
else:
|
307 |
+
raise IndexError("Invalid indices (%s, %s)" % (i, j))
|
308 |
+
elif isinstance(key, (SYMPY_INTS, Integer)):
|
309 |
+
# row-wise decomposition of matrix
|
310 |
+
rows, cols = self.shape
|
311 |
+
# allow single indexing if number of columns is known
|
312 |
+
if not isinstance(cols, Integer):
|
313 |
+
raise IndexError(filldedent('''
|
314 |
+
Single indexing is only supported when the number
|
315 |
+
of columns is known.'''))
|
316 |
+
key = _sympify(key)
|
317 |
+
i = key // cols
|
318 |
+
j = key % cols
|
319 |
+
if self.valid_index(i, j) != False:
|
320 |
+
return self._entry(i, j)
|
321 |
+
else:
|
322 |
+
raise IndexError("Invalid index %s" % key)
|
323 |
+
elif isinstance(key, (Symbol, Expr)):
|
324 |
+
raise IndexError(filldedent('''
|
325 |
+
Only integers may be used when addressing the matrix
|
326 |
+
with a single index.'''))
|
327 |
+
raise IndexError("Invalid index, wanted %s[i,j]" % self)
|
328 |
+
|
329 |
+
def _is_shape_symbolic(self) -> bool:
|
330 |
+
return (not isinstance(self.rows, (SYMPY_INTS, Integer))
|
331 |
+
or not isinstance(self.cols, (SYMPY_INTS, Integer)))
|
332 |
+
|
333 |
+
def as_explicit(self):
|
334 |
+
"""
|
335 |
+
Returns a dense Matrix with elements represented explicitly
|
336 |
+
|
337 |
+
Returns an object of type ImmutableDenseMatrix.
|
338 |
+
|
339 |
+
Examples
|
340 |
+
========
|
341 |
+
|
342 |
+
>>> from sympy import Identity
|
343 |
+
>>> I = Identity(3)
|
344 |
+
>>> I
|
345 |
+
I
|
346 |
+
>>> I.as_explicit()
|
347 |
+
Matrix([
|
348 |
+
[1, 0, 0],
|
349 |
+
[0, 1, 0],
|
350 |
+
[0, 0, 1]])
|
351 |
+
|
352 |
+
See Also
|
353 |
+
========
|
354 |
+
as_mutable: returns mutable Matrix type
|
355 |
+
|
356 |
+
"""
|
357 |
+
if self._is_shape_symbolic():
|
358 |
+
raise ValueError(
|
359 |
+
'Matrix with symbolic shape '
|
360 |
+
'cannot be represented explicitly.')
|
361 |
+
from sympy.matrices.immutable import ImmutableDenseMatrix
|
362 |
+
return ImmutableDenseMatrix([[self[i, j]
|
363 |
+
for j in range(self.cols)]
|
364 |
+
for i in range(self.rows)])
|
365 |
+
|
366 |
+
def as_mutable(self):
|
367 |
+
"""
|
368 |
+
Returns a dense, mutable matrix with elements represented explicitly
|
369 |
+
|
370 |
+
Examples
|
371 |
+
========
|
372 |
+
|
373 |
+
>>> from sympy import Identity
|
374 |
+
>>> I = Identity(3)
|
375 |
+
>>> I
|
376 |
+
I
|
377 |
+
>>> I.shape
|
378 |
+
(3, 3)
|
379 |
+
>>> I.as_mutable()
|
380 |
+
Matrix([
|
381 |
+
[1, 0, 0],
|
382 |
+
[0, 1, 0],
|
383 |
+
[0, 0, 1]])
|
384 |
+
|
385 |
+
See Also
|
386 |
+
========
|
387 |
+
as_explicit: returns ImmutableDenseMatrix
|
388 |
+
"""
|
389 |
+
return self.as_explicit().as_mutable()
|
390 |
+
|
391 |
+
def __array__(self, dtype=object, copy=None):
|
392 |
+
if copy is not None and not copy:
|
393 |
+
raise TypeError("Cannot implement copy=False when converting Matrix to ndarray")
|
394 |
+
from numpy import empty
|
395 |
+
a = empty(self.shape, dtype=object)
|
396 |
+
for i in range(self.rows):
|
397 |
+
for j in range(self.cols):
|
398 |
+
a[i, j] = self[i, j]
|
399 |
+
return a
|
400 |
+
|
401 |
+
def equals(self, other):
|
402 |
+
"""
|
403 |
+
Test elementwise equality between matrices, potentially of different
|
404 |
+
types
|
405 |
+
|
406 |
+
>>> from sympy import Identity, eye
|
407 |
+
>>> Identity(3).equals(eye(3))
|
408 |
+
True
|
409 |
+
"""
|
410 |
+
return self.as_explicit().equals(other)
|
411 |
+
|
412 |
+
def canonicalize(self):
|
413 |
+
return self
|
414 |
+
|
415 |
+
def as_coeff_mmul(self):
|
416 |
+
return S.One, MatMul(self)
|
417 |
+
|
418 |
+
@staticmethod
|
419 |
+
def from_index_summation(expr, first_index=None, last_index=None, dimensions=None):
|
420 |
+
r"""
|
421 |
+
Parse expression of matrices with explicitly summed indices into a
|
422 |
+
matrix expression without indices, if possible.
|
423 |
+
|
424 |
+
This transformation expressed in mathematical notation:
|
425 |
+
|
426 |
+
`\sum_{j=0}^{N-1} A_{i,j} B_{j,k} \Longrightarrow \mathbf{A}\cdot \mathbf{B}`
|
427 |
+
|
428 |
+
Optional parameter ``first_index``: specify which free index to use as
|
429 |
+
the index starting the expression.
|
430 |
+
|
431 |
+
Examples
|
432 |
+
========
|
433 |
+
|
434 |
+
>>> from sympy import MatrixSymbol, MatrixExpr, Sum
|
435 |
+
>>> from sympy.abc import i, j, k, l, N
|
436 |
+
>>> A = MatrixSymbol("A", N, N)
|
437 |
+
>>> B = MatrixSymbol("B", N, N)
|
438 |
+
>>> expr = Sum(A[i, j]*B[j, k], (j, 0, N-1))
|
439 |
+
>>> MatrixExpr.from_index_summation(expr)
|
440 |
+
A*B
|
441 |
+
|
442 |
+
Transposition is detected:
|
443 |
+
|
444 |
+
>>> expr = Sum(A[j, i]*B[j, k], (j, 0, N-1))
|
445 |
+
>>> MatrixExpr.from_index_summation(expr)
|
446 |
+
A.T*B
|
447 |
+
|
448 |
+
Detect the trace:
|
449 |
+
|
450 |
+
>>> expr = Sum(A[i, i], (i, 0, N-1))
|
451 |
+
>>> MatrixExpr.from_index_summation(expr)
|
452 |
+
Trace(A)
|
453 |
+
|
454 |
+
More complicated expressions:
|
455 |
+
|
456 |
+
>>> expr = Sum(A[i, j]*B[k, j]*A[l, k], (j, 0, N-1), (k, 0, N-1))
|
457 |
+
>>> MatrixExpr.from_index_summation(expr)
|
458 |
+
A*B.T*A.T
|
459 |
+
"""
|
460 |
+
from sympy.tensor.array.expressions.from_indexed_to_array import convert_indexed_to_array
|
461 |
+
from sympy.tensor.array.expressions.from_array_to_matrix import convert_array_to_matrix
|
462 |
+
first_indices = []
|
463 |
+
if first_index is not None:
|
464 |
+
first_indices.append(first_index)
|
465 |
+
if last_index is not None:
|
466 |
+
first_indices.append(last_index)
|
467 |
+
arr = convert_indexed_to_array(expr, first_indices=first_indices)
|
468 |
+
return convert_array_to_matrix(arr)
|
469 |
+
|
470 |
+
def applyfunc(self, func):
|
471 |
+
from .applyfunc import ElementwiseApplyFunction
|
472 |
+
return ElementwiseApplyFunction(func, self)
|
473 |
+
|
474 |
+
|
475 |
+
@dispatch(MatrixExpr, Expr)
|
476 |
+
def _eval_is_eq(lhs, rhs): # noqa:F811
|
477 |
+
return False
|
478 |
+
|
479 |
+
@dispatch(MatrixExpr, MatrixExpr) # type: ignore
|
480 |
+
def _eval_is_eq(lhs, rhs): # noqa:F811
|
481 |
+
if lhs.shape != rhs.shape:
|
482 |
+
return False
|
483 |
+
if (lhs - rhs).is_ZeroMatrix:
|
484 |
+
return True
|
485 |
+
|
486 |
+
def get_postprocessor(cls):
|
487 |
+
def _postprocessor(expr):
|
488 |
+
# To avoid circular imports, we can't have MatMul/MatAdd on the top level
|
489 |
+
mat_class = {Mul: MatMul, Add: MatAdd}[cls]
|
490 |
+
nonmatrices = []
|
491 |
+
matrices = []
|
492 |
+
for term in expr.args:
|
493 |
+
if isinstance(term, MatrixExpr):
|
494 |
+
matrices.append(term)
|
495 |
+
else:
|
496 |
+
nonmatrices.append(term)
|
497 |
+
|
498 |
+
if not matrices:
|
499 |
+
return cls._from_args(nonmatrices)
|
500 |
+
|
501 |
+
if nonmatrices:
|
502 |
+
if cls == Mul:
|
503 |
+
for i in range(len(matrices)):
|
504 |
+
if not matrices[i].is_MatrixExpr:
|
505 |
+
# If one of the matrices explicit, absorb the scalar into it
|
506 |
+
# (doit will combine all explicit matrices into one, so it
|
507 |
+
# doesn't matter which)
|
508 |
+
matrices[i] = matrices[i].__mul__(cls._from_args(nonmatrices))
|
509 |
+
nonmatrices = []
|
510 |
+
break
|
511 |
+
|
512 |
+
else:
|
513 |
+
# Maintain the ability to create Add(scalar, matrix) without
|
514 |
+
# raising an exception. That way different algorithms can
|
515 |
+
# replace matrix expressions with non-commutative symbols to
|
516 |
+
# manipulate them like non-commutative scalars.
|
517 |
+
return cls._from_args(nonmatrices + [mat_class(*matrices).doit(deep=False)])
|
518 |
+
|
519 |
+
if mat_class == MatAdd:
|
520 |
+
return mat_class(*matrices).doit(deep=False)
|
521 |
+
return mat_class(cls._from_args(nonmatrices), *matrices).doit(deep=False)
|
522 |
+
return _postprocessor
|
523 |
+
|
524 |
+
|
525 |
+
Basic._constructor_postprocessor_mapping[MatrixExpr] = {
|
526 |
+
"Mul": [get_postprocessor(Mul)],
|
527 |
+
"Add": [get_postprocessor(Add)],
|
528 |
+
}
|
529 |
+
|
530 |
+
|
531 |
+
def _matrix_derivative(expr, x, old_algorithm=False):
|
532 |
+
|
533 |
+
if isinstance(expr, MatrixBase) or isinstance(x, MatrixBase):
|
534 |
+
# Do not use array expressions for explicit matrices:
|
535 |
+
old_algorithm = True
|
536 |
+
|
537 |
+
if old_algorithm:
|
538 |
+
return _matrix_derivative_old_algorithm(expr, x)
|
539 |
+
|
540 |
+
from sympy.tensor.array.expressions.from_matrix_to_array import convert_matrix_to_array
|
541 |
+
from sympy.tensor.array.expressions.arrayexpr_derivatives import array_derive
|
542 |
+
from sympy.tensor.array.expressions.from_array_to_matrix import convert_array_to_matrix
|
543 |
+
|
544 |
+
array_expr = convert_matrix_to_array(expr)
|
545 |
+
diff_array_expr = array_derive(array_expr, x)
|
546 |
+
diff_matrix_expr = convert_array_to_matrix(diff_array_expr)
|
547 |
+
return diff_matrix_expr
|
548 |
+
|
549 |
+
|
550 |
+
def _matrix_derivative_old_algorithm(expr, x):
|
551 |
+
from sympy.tensor.array.array_derivatives import ArrayDerivative
|
552 |
+
lines = expr._eval_derivative_matrix_lines(x)
|
553 |
+
|
554 |
+
parts = [i.build() for i in lines]
|
555 |
+
|
556 |
+
from sympy.tensor.array.expressions.from_array_to_matrix import convert_array_to_matrix
|
557 |
+
|
558 |
+
parts = [[convert_array_to_matrix(j) for j in i] for i in parts]
|
559 |
+
|
560 |
+
def _get_shape(elem):
|
561 |
+
if isinstance(elem, MatrixExpr):
|
562 |
+
return elem.shape
|
563 |
+
return 1, 1
|
564 |
+
|
565 |
+
def get_rank(parts):
|
566 |
+
return sum(j not in (1, None) for i in parts for j in _get_shape(i))
|
567 |
+
|
568 |
+
ranks = [get_rank(i) for i in parts]
|
569 |
+
rank = ranks[0]
|
570 |
+
|
571 |
+
def contract_one_dims(parts):
|
572 |
+
if len(parts) == 1:
|
573 |
+
return parts[0]
|
574 |
+
else:
|
575 |
+
p1, p2 = parts[:2]
|
576 |
+
if p2.is_Matrix:
|
577 |
+
p2 = p2.T
|
578 |
+
if p1 == Identity(1):
|
579 |
+
pbase = p2
|
580 |
+
elif p2 == Identity(1):
|
581 |
+
pbase = p1
|
582 |
+
else:
|
583 |
+
pbase = p1*p2
|
584 |
+
if len(parts) == 2:
|
585 |
+
return pbase
|
586 |
+
else: # len(parts) > 2
|
587 |
+
if pbase.is_Matrix:
|
588 |
+
raise ValueError("")
|
589 |
+
return pbase*Mul.fromiter(parts[2:])
|
590 |
+
|
591 |
+
if rank <= 2:
|
592 |
+
return Add.fromiter([contract_one_dims(i) for i in parts])
|
593 |
+
|
594 |
+
return ArrayDerivative(expr, x)
|
595 |
+
|
596 |
+
|
597 |
+
class MatrixElement(Expr):
|
598 |
+
parent = property(lambda self: self.args[0])
|
599 |
+
i = property(lambda self: self.args[1])
|
600 |
+
j = property(lambda self: self.args[2])
|
601 |
+
_diff_wrt = True
|
602 |
+
is_symbol = True
|
603 |
+
is_commutative = True
|
604 |
+
|
605 |
+
def __new__(cls, name, n, m):
|
606 |
+
n, m = map(_sympify, (n, m))
|
607 |
+
if isinstance(name, str):
|
608 |
+
name = Symbol(name)
|
609 |
+
else:
|
610 |
+
if isinstance(name, MatrixBase):
|
611 |
+
if n.is_Integer and m.is_Integer:
|
612 |
+
return name[n, m]
|
613 |
+
name = _sympify(name) # change mutable into immutable
|
614 |
+
else:
|
615 |
+
name = _sympify(name)
|
616 |
+
if not isinstance(name.kind, MatrixKind):
|
617 |
+
raise TypeError("First argument of MatrixElement should be a matrix")
|
618 |
+
if not getattr(name, 'valid_index', lambda n, m: True)(n, m):
|
619 |
+
raise IndexError('indices out of range')
|
620 |
+
obj = Expr.__new__(cls, name, n, m)
|
621 |
+
return obj
|
622 |
+
|
623 |
+
@property
|
624 |
+
def symbol(self):
|
625 |
+
return self.args[0]
|
626 |
+
|
627 |
+
def doit(self, **hints):
|
628 |
+
deep = hints.get('deep', True)
|
629 |
+
if deep:
|
630 |
+
args = [arg.doit(**hints) for arg in self.args]
|
631 |
+
else:
|
632 |
+
args = self.args
|
633 |
+
return args[0][args[1], args[2]]
|
634 |
+
|
635 |
+
@property
|
636 |
+
def indices(self):
|
637 |
+
return self.args[1:]
|
638 |
+
|
639 |
+
def _eval_derivative(self, v):
|
640 |
+
|
641 |
+
if not isinstance(v, MatrixElement):
|
642 |
+
return self.parent.diff(v)[self.i, self.j]
|
643 |
+
|
644 |
+
M = self.args[0]
|
645 |
+
|
646 |
+
m, n = self.parent.shape
|
647 |
+
|
648 |
+
if M == v.args[0]:
|
649 |
+
return KroneckerDelta(self.args[1], v.args[1], (0, m-1)) * \
|
650 |
+
KroneckerDelta(self.args[2], v.args[2], (0, n-1))
|
651 |
+
|
652 |
+
if isinstance(M, Inverse):
|
653 |
+
from sympy.concrete.summations import Sum
|
654 |
+
i, j = self.args[1:]
|
655 |
+
i1, i2 = symbols("z1, z2", cls=Dummy)
|
656 |
+
Y = M.args[0]
|
657 |
+
r1, r2 = Y.shape
|
658 |
+
return -Sum(M[i, i1]*Y[i1, i2].diff(v)*M[i2, j], (i1, 0, r1-1), (i2, 0, r2-1))
|
659 |
+
|
660 |
+
if self.has(v.args[0]):
|
661 |
+
return None
|
662 |
+
|
663 |
+
return S.Zero
|
664 |
+
|
665 |
+
|
666 |
+
class MatrixSymbol(MatrixExpr):
|
667 |
+
"""Symbolic representation of a Matrix object
|
668 |
+
|
669 |
+
Creates a SymPy Symbol to represent a Matrix. This matrix has a shape and
|
670 |
+
can be included in Matrix Expressions
|
671 |
+
|
672 |
+
Examples
|
673 |
+
========
|
674 |
+
|
675 |
+
>>> from sympy import MatrixSymbol, Identity
|
676 |
+
>>> A = MatrixSymbol('A', 3, 4) # A 3 by 4 Matrix
|
677 |
+
>>> B = MatrixSymbol('B', 4, 3) # A 4 by 3 Matrix
|
678 |
+
>>> A.shape
|
679 |
+
(3, 4)
|
680 |
+
>>> 2*A*B + Identity(3)
|
681 |
+
I + 2*A*B
|
682 |
+
"""
|
683 |
+
is_commutative = False
|
684 |
+
is_symbol = True
|
685 |
+
_diff_wrt = True
|
686 |
+
|
687 |
+
def __new__(cls, name, n, m):
|
688 |
+
n, m = _sympify(n), _sympify(m)
|
689 |
+
|
690 |
+
cls._check_dim(m)
|
691 |
+
cls._check_dim(n)
|
692 |
+
|
693 |
+
if isinstance(name, str):
|
694 |
+
name = Str(name)
|
695 |
+
obj = Basic.__new__(cls, name, n, m)
|
696 |
+
return obj
|
697 |
+
|
698 |
+
@property
|
699 |
+
def shape(self):
|
700 |
+
return self.args[1], self.args[2]
|
701 |
+
|
702 |
+
@property
|
703 |
+
def name(self):
|
704 |
+
return self.args[0].name
|
705 |
+
|
706 |
+
def _entry(self, i, j, **kwargs):
|
707 |
+
return MatrixElement(self, i, j)
|
708 |
+
|
709 |
+
@property
|
710 |
+
def free_symbols(self):
|
711 |
+
return {self}
|
712 |
+
|
713 |
+
def _eval_simplify(self, **kwargs):
|
714 |
+
return self
|
715 |
+
|
716 |
+
def _eval_derivative(self, x):
|
717 |
+
# x is a scalar:
|
718 |
+
return ZeroMatrix(self.shape[0], self.shape[1])
|
719 |
+
|
720 |
+
def _eval_derivative_matrix_lines(self, x):
|
721 |
+
if self != x:
|
722 |
+
first = ZeroMatrix(x.shape[0], self.shape[0]) if self.shape[0] != 1 else S.Zero
|
723 |
+
second = ZeroMatrix(x.shape[1], self.shape[1]) if self.shape[1] != 1 else S.Zero
|
724 |
+
return [_LeftRightArgs(
|
725 |
+
[first, second],
|
726 |
+
)]
|
727 |
+
else:
|
728 |
+
first = Identity(self.shape[0]) if self.shape[0] != 1 else S.One
|
729 |
+
second = Identity(self.shape[1]) if self.shape[1] != 1 else S.One
|
730 |
+
return [_LeftRightArgs(
|
731 |
+
[first, second],
|
732 |
+
)]
|
733 |
+
|
734 |
+
|
735 |
+
def matrix_symbols(expr):
|
736 |
+
return [sym for sym in expr.free_symbols if sym.is_Matrix]
|
737 |
+
|
738 |
+
|
739 |
+
class _LeftRightArgs:
|
740 |
+
r"""
|
741 |
+
Helper class to compute matrix derivatives.
|
742 |
+
|
743 |
+
The logic: when an expression is derived by a matrix `X_{mn}`, two lines of
|
744 |
+
matrix multiplications are created: the one contracted to `m` (first line),
|
745 |
+
and the one contracted to `n` (second line).
|
746 |
+
|
747 |
+
Transposition flips the side by which new matrices are connected to the
|
748 |
+
lines.
|
749 |
+
|
750 |
+
The trace connects the end of the two lines.
|
751 |
+
"""
|
752 |
+
|
753 |
+
def __init__(self, lines, higher=S.One):
|
754 |
+
self._lines = list(lines)
|
755 |
+
self._first_pointer_parent = self._lines
|
756 |
+
self._first_pointer_index = 0
|
757 |
+
self._first_line_index = 0
|
758 |
+
self._second_pointer_parent = self._lines
|
759 |
+
self._second_pointer_index = 1
|
760 |
+
self._second_line_index = 1
|
761 |
+
self.higher = higher
|
762 |
+
|
763 |
+
@property
|
764 |
+
def first_pointer(self):
|
765 |
+
return self._first_pointer_parent[self._first_pointer_index]
|
766 |
+
|
767 |
+
@first_pointer.setter
|
768 |
+
def first_pointer(self, value):
|
769 |
+
self._first_pointer_parent[self._first_pointer_index] = value
|
770 |
+
|
771 |
+
@property
|
772 |
+
def second_pointer(self):
|
773 |
+
return self._second_pointer_parent[self._second_pointer_index]
|
774 |
+
|
775 |
+
@second_pointer.setter
|
776 |
+
def second_pointer(self, value):
|
777 |
+
self._second_pointer_parent[self._second_pointer_index] = value
|
778 |
+
|
779 |
+
def __repr__(self):
|
780 |
+
built = [self._build(i) for i in self._lines]
|
781 |
+
return "_LeftRightArgs(lines=%s, higher=%s)" % (
|
782 |
+
built,
|
783 |
+
self.higher,
|
784 |
+
)
|
785 |
+
|
786 |
+
def transpose(self):
|
787 |
+
self._first_pointer_parent, self._second_pointer_parent = self._second_pointer_parent, self._first_pointer_parent
|
788 |
+
self._first_pointer_index, self._second_pointer_index = self._second_pointer_index, self._first_pointer_index
|
789 |
+
self._first_line_index, self._second_line_index = self._second_line_index, self._first_line_index
|
790 |
+
return self
|
791 |
+
|
792 |
+
@staticmethod
|
793 |
+
def _build(expr):
|
794 |
+
if isinstance(expr, ExprBuilder):
|
795 |
+
return expr.build()
|
796 |
+
if isinstance(expr, list):
|
797 |
+
if len(expr) == 1:
|
798 |
+
return expr[0]
|
799 |
+
else:
|
800 |
+
return expr[0](*[_LeftRightArgs._build(i) for i in expr[1]])
|
801 |
+
else:
|
802 |
+
return expr
|
803 |
+
|
804 |
+
def build(self):
|
805 |
+
data = [self._build(i) for i in self._lines]
|
806 |
+
if self.higher != 1:
|
807 |
+
data += [self._build(self.higher)]
|
808 |
+
data = list(data)
|
809 |
+
return data
|
810 |
+
|
811 |
+
def matrix_form(self):
|
812 |
+
if self.first != 1 and self.higher != 1:
|
813 |
+
raise ValueError("higher dimensional array cannot be represented")
|
814 |
+
|
815 |
+
def _get_shape(elem):
|
816 |
+
if isinstance(elem, MatrixExpr):
|
817 |
+
return elem.shape
|
818 |
+
return (None, None)
|
819 |
+
|
820 |
+
if _get_shape(self.first)[1] != _get_shape(self.second)[1]:
|
821 |
+
# Remove one-dimensional identity matrices:
|
822 |
+
# (this is needed by `a.diff(a)` where `a` is a vector)
|
823 |
+
if _get_shape(self.second) == (1, 1):
|
824 |
+
return self.first*self.second[0, 0]
|
825 |
+
if _get_shape(self.first) == (1, 1):
|
826 |
+
return self.first[1, 1]*self.second.T
|
827 |
+
raise ValueError("incompatible shapes")
|
828 |
+
if self.first != 1:
|
829 |
+
return self.first*self.second.T
|
830 |
+
else:
|
831 |
+
return self.higher
|
832 |
+
|
833 |
+
def rank(self):
|
834 |
+
"""
|
835 |
+
Number of dimensions different from trivial (warning: not related to
|
836 |
+
matrix rank).
|
837 |
+
"""
|
838 |
+
rank = 0
|
839 |
+
if self.first != 1:
|
840 |
+
rank += sum(i != 1 for i in self.first.shape)
|
841 |
+
if self.second != 1:
|
842 |
+
rank += sum(i != 1 for i in self.second.shape)
|
843 |
+
if self.higher != 1:
|
844 |
+
rank += 2
|
845 |
+
return rank
|
846 |
+
|
847 |
+
def _multiply_pointer(self, pointer, other):
|
848 |
+
from ...tensor.array.expressions.array_expressions import ArrayTensorProduct
|
849 |
+
from ...tensor.array.expressions.array_expressions import ArrayContraction
|
850 |
+
|
851 |
+
subexpr = ExprBuilder(
|
852 |
+
ArrayContraction,
|
853 |
+
[
|
854 |
+
ExprBuilder(
|
855 |
+
ArrayTensorProduct,
|
856 |
+
[
|
857 |
+
pointer,
|
858 |
+
other
|
859 |
+
]
|
860 |
+
),
|
861 |
+
(1, 2)
|
862 |
+
],
|
863 |
+
validator=ArrayContraction._validate
|
864 |
+
)
|
865 |
+
|
866 |
+
return subexpr
|
867 |
+
|
868 |
+
def append_first(self, other):
|
869 |
+
self.first_pointer *= other
|
870 |
+
|
871 |
+
def append_second(self, other):
|
872 |
+
self.second_pointer *= other
|
873 |
+
|
874 |
+
|
875 |
+
def _make_matrix(x):
|
876 |
+
from sympy.matrices.immutable import ImmutableDenseMatrix
|
877 |
+
if isinstance(x, MatrixExpr):
|
878 |
+
return x
|
879 |
+
return ImmutableDenseMatrix([[x]])
|
880 |
+
|
881 |
+
|
882 |
+
from .matmul import MatMul
|
883 |
+
from .matadd import MatAdd
|
884 |
+
from .matpow import MatPow
|
885 |
+
from .transpose import Transpose
|
886 |
+
from .inverse import Inverse
|
887 |
+
from .special import ZeroMatrix, Identity
|
888 |
+
from .determinant import Determinant
|
.venv/lib/python3.11/site-packages/sympy/matrices/expressions/matpow.py
ADDED
@@ -0,0 +1,150 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from .matexpr import MatrixExpr
|
2 |
+
from .special import Identity
|
3 |
+
from sympy.core import S
|
4 |
+
from sympy.core.expr import ExprBuilder
|
5 |
+
from sympy.core.cache import cacheit
|
6 |
+
from sympy.core.power import Pow
|
7 |
+
from sympy.core.sympify import _sympify
|
8 |
+
from sympy.matrices import MatrixBase
|
9 |
+
from sympy.matrices.exceptions import NonSquareMatrixError
|
10 |
+
|
11 |
+
|
12 |
+
class MatPow(MatrixExpr):
|
13 |
+
def __new__(cls, base, exp, evaluate=False, **options):
|
14 |
+
base = _sympify(base)
|
15 |
+
if not base.is_Matrix:
|
16 |
+
raise TypeError("MatPow base should be a matrix")
|
17 |
+
|
18 |
+
if base.is_square is False:
|
19 |
+
raise NonSquareMatrixError("Power of non-square matrix %s" % base)
|
20 |
+
|
21 |
+
exp = _sympify(exp)
|
22 |
+
obj = super().__new__(cls, base, exp)
|
23 |
+
|
24 |
+
if evaluate:
|
25 |
+
obj = obj.doit(deep=False)
|
26 |
+
|
27 |
+
return obj
|
28 |
+
|
29 |
+
@property
|
30 |
+
def base(self):
|
31 |
+
return self.args[0]
|
32 |
+
|
33 |
+
@property
|
34 |
+
def exp(self):
|
35 |
+
return self.args[1]
|
36 |
+
|
37 |
+
@property
|
38 |
+
def shape(self):
|
39 |
+
return self.base.shape
|
40 |
+
|
41 |
+
@cacheit
|
42 |
+
def _get_explicit_matrix(self):
|
43 |
+
return self.base.as_explicit()**self.exp
|
44 |
+
|
45 |
+
def _entry(self, i, j, **kwargs):
|
46 |
+
from sympy.matrices.expressions import MatMul
|
47 |
+
A = self.doit()
|
48 |
+
if isinstance(A, MatPow):
|
49 |
+
# We still have a MatPow, make an explicit MatMul out of it.
|
50 |
+
if A.exp.is_Integer and A.exp.is_positive:
|
51 |
+
A = MatMul(*[A.base for k in range(A.exp)])
|
52 |
+
elif not self._is_shape_symbolic():
|
53 |
+
return A._get_explicit_matrix()[i, j]
|
54 |
+
else:
|
55 |
+
# Leave the expression unevaluated:
|
56 |
+
from sympy.matrices.expressions.matexpr import MatrixElement
|
57 |
+
return MatrixElement(self, i, j)
|
58 |
+
return A[i, j]
|
59 |
+
|
60 |
+
def doit(self, **hints):
|
61 |
+
if hints.get('deep', True):
|
62 |
+
base, exp = (arg.doit(**hints) for arg in self.args)
|
63 |
+
else:
|
64 |
+
base, exp = self.args
|
65 |
+
|
66 |
+
# combine all powers, e.g. (A ** 2) ** 3 -> A ** 6
|
67 |
+
while isinstance(base, MatPow):
|
68 |
+
exp *= base.args[1]
|
69 |
+
base = base.args[0]
|
70 |
+
|
71 |
+
if isinstance(base, MatrixBase):
|
72 |
+
# Delegate
|
73 |
+
return base ** exp
|
74 |
+
|
75 |
+
# Handle simple cases so that _eval_power() in MatrixExpr sub-classes can ignore them
|
76 |
+
if exp == S.One:
|
77 |
+
return base
|
78 |
+
if exp == S.Zero:
|
79 |
+
return Identity(base.rows)
|
80 |
+
if exp == S.NegativeOne:
|
81 |
+
from sympy.matrices.expressions import Inverse
|
82 |
+
return Inverse(base).doit(**hints)
|
83 |
+
|
84 |
+
eval_power = getattr(base, '_eval_power', None)
|
85 |
+
if eval_power is not None:
|
86 |
+
return eval_power(exp)
|
87 |
+
|
88 |
+
return MatPow(base, exp)
|
89 |
+
|
90 |
+
def _eval_transpose(self):
|
91 |
+
base, exp = self.args
|
92 |
+
return MatPow(base.transpose(), exp)
|
93 |
+
|
94 |
+
def _eval_adjoint(self):
|
95 |
+
base, exp = self.args
|
96 |
+
return MatPow(base.adjoint(), exp)
|
97 |
+
|
98 |
+
def _eval_conjugate(self):
|
99 |
+
base, exp = self.args
|
100 |
+
return MatPow(base.conjugate(), exp)
|
101 |
+
|
102 |
+
def _eval_derivative(self, x):
|
103 |
+
return Pow._eval_derivative(self, x)
|
104 |
+
|
105 |
+
def _eval_derivative_matrix_lines(self, x):
|
106 |
+
from sympy.tensor.array.expressions.array_expressions import ArrayContraction
|
107 |
+
from ...tensor.array.expressions.array_expressions import ArrayTensorProduct
|
108 |
+
from .matmul import MatMul
|
109 |
+
from .inverse import Inverse
|
110 |
+
exp = self.exp
|
111 |
+
if self.base.shape == (1, 1) and not exp.has(x):
|
112 |
+
lr = self.base._eval_derivative_matrix_lines(x)
|
113 |
+
for i in lr:
|
114 |
+
subexpr = ExprBuilder(
|
115 |
+
ArrayContraction,
|
116 |
+
[
|
117 |
+
ExprBuilder(
|
118 |
+
ArrayTensorProduct,
|
119 |
+
[
|
120 |
+
Identity(1),
|
121 |
+
i._lines[0],
|
122 |
+
exp*self.base**(exp-1),
|
123 |
+
i._lines[1],
|
124 |
+
Identity(1),
|
125 |
+
]
|
126 |
+
),
|
127 |
+
(0, 3, 4), (5, 7, 8)
|
128 |
+
],
|
129 |
+
validator=ArrayContraction._validate
|
130 |
+
)
|
131 |
+
i._first_pointer_parent = subexpr.args[0].args
|
132 |
+
i._first_pointer_index = 0
|
133 |
+
i._second_pointer_parent = subexpr.args[0].args
|
134 |
+
i._second_pointer_index = 4
|
135 |
+
i._lines = [subexpr]
|
136 |
+
return lr
|
137 |
+
if (exp > 0) == True:
|
138 |
+
newexpr = MatMul.fromiter([self.base for i in range(exp)])
|
139 |
+
elif (exp == -1) == True:
|
140 |
+
return Inverse(self.base)._eval_derivative_matrix_lines(x)
|
141 |
+
elif (exp < 0) == True:
|
142 |
+
newexpr = MatMul.fromiter([Inverse(self.base) for i in range(-exp)])
|
143 |
+
elif (exp == 0) == True:
|
144 |
+
return self.doit()._eval_derivative_matrix_lines(x)
|
145 |
+
else:
|
146 |
+
raise NotImplementedError("cannot evaluate %s derived by %s" % (self, x))
|
147 |
+
return newexpr._eval_derivative_matrix_lines(x)
|
148 |
+
|
149 |
+
def _eval_inverse(self):
|
150 |
+
return MatPow(self.base, -self.exp)
|
.venv/lib/python3.11/site-packages/sympy/matrices/expressions/slice.py
ADDED
@@ -0,0 +1,114 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from sympy.matrices.expressions.matexpr import MatrixExpr
|
2 |
+
from sympy.core.basic import Basic
|
3 |
+
from sympy.core.containers import Tuple
|
4 |
+
from sympy.functions.elementary.integers import floor
|
5 |
+
|
6 |
+
def normalize(i, parentsize):
|
7 |
+
if isinstance(i, slice):
|
8 |
+
i = (i.start, i.stop, i.step)
|
9 |
+
if not isinstance(i, (tuple, list, Tuple)):
|
10 |
+
if (i < 0) == True:
|
11 |
+
i += parentsize
|
12 |
+
i = (i, i+1, 1)
|
13 |
+
i = list(i)
|
14 |
+
if len(i) == 2:
|
15 |
+
i.append(1)
|
16 |
+
start, stop, step = i
|
17 |
+
start = start or 0
|
18 |
+
if stop is None:
|
19 |
+
stop = parentsize
|
20 |
+
if (start < 0) == True:
|
21 |
+
start += parentsize
|
22 |
+
if (stop < 0) == True:
|
23 |
+
stop += parentsize
|
24 |
+
step = step or 1
|
25 |
+
|
26 |
+
if ((stop - start) * step < 1) == True:
|
27 |
+
raise IndexError()
|
28 |
+
|
29 |
+
return (start, stop, step)
|
30 |
+
|
31 |
+
class MatrixSlice(MatrixExpr):
|
32 |
+
""" A MatrixSlice of a Matrix Expression
|
33 |
+
|
34 |
+
Examples
|
35 |
+
========
|
36 |
+
|
37 |
+
>>> from sympy import MatrixSlice, ImmutableMatrix
|
38 |
+
>>> M = ImmutableMatrix(4, 4, range(16))
|
39 |
+
>>> M
|
40 |
+
Matrix([
|
41 |
+
[ 0, 1, 2, 3],
|
42 |
+
[ 4, 5, 6, 7],
|
43 |
+
[ 8, 9, 10, 11],
|
44 |
+
[12, 13, 14, 15]])
|
45 |
+
|
46 |
+
>>> B = MatrixSlice(M, (0, 2), (2, 4))
|
47 |
+
>>> ImmutableMatrix(B)
|
48 |
+
Matrix([
|
49 |
+
[2, 3],
|
50 |
+
[6, 7]])
|
51 |
+
"""
|
52 |
+
parent = property(lambda self: self.args[0])
|
53 |
+
rowslice = property(lambda self: self.args[1])
|
54 |
+
colslice = property(lambda self: self.args[2])
|
55 |
+
|
56 |
+
def __new__(cls, parent, rowslice, colslice):
|
57 |
+
rowslice = normalize(rowslice, parent.shape[0])
|
58 |
+
colslice = normalize(colslice, parent.shape[1])
|
59 |
+
if not (len(rowslice) == len(colslice) == 3):
|
60 |
+
raise IndexError()
|
61 |
+
if ((0 > rowslice[0]) == True or
|
62 |
+
(parent.shape[0] < rowslice[1]) == True or
|
63 |
+
(0 > colslice[0]) == True or
|
64 |
+
(parent.shape[1] < colslice[1]) == True):
|
65 |
+
raise IndexError()
|
66 |
+
if isinstance(parent, MatrixSlice):
|
67 |
+
return mat_slice_of_slice(parent, rowslice, colslice)
|
68 |
+
return Basic.__new__(cls, parent, Tuple(*rowslice), Tuple(*colslice))
|
69 |
+
|
70 |
+
@property
|
71 |
+
def shape(self):
|
72 |
+
rows = self.rowslice[1] - self.rowslice[0]
|
73 |
+
rows = rows if self.rowslice[2] == 1 else floor(rows/self.rowslice[2])
|
74 |
+
cols = self.colslice[1] - self.colslice[0]
|
75 |
+
cols = cols if self.colslice[2] == 1 else floor(cols/self.colslice[2])
|
76 |
+
return rows, cols
|
77 |
+
|
78 |
+
def _entry(self, i, j, **kwargs):
|
79 |
+
return self.parent._entry(i*self.rowslice[2] + self.rowslice[0],
|
80 |
+
j*self.colslice[2] + self.colslice[0],
|
81 |
+
**kwargs)
|
82 |
+
|
83 |
+
@property
|
84 |
+
def on_diag(self):
|
85 |
+
return self.rowslice == self.colslice
|
86 |
+
|
87 |
+
|
88 |
+
def slice_of_slice(s, t):
|
89 |
+
start1, stop1, step1 = s
|
90 |
+
start2, stop2, step2 = t
|
91 |
+
|
92 |
+
start = start1 + start2*step1
|
93 |
+
step = step1 * step2
|
94 |
+
stop = start1 + step1*stop2
|
95 |
+
|
96 |
+
if stop > stop1:
|
97 |
+
raise IndexError()
|
98 |
+
|
99 |
+
return start, stop, step
|
100 |
+
|
101 |
+
|
102 |
+
def mat_slice_of_slice(parent, rowslice, colslice):
|
103 |
+
""" Collapse nested matrix slices
|
104 |
+
|
105 |
+
>>> from sympy import MatrixSymbol
|
106 |
+
>>> X = MatrixSymbol('X', 10, 10)
|
107 |
+
>>> X[:, 1:5][5:8, :]
|
108 |
+
X[5:8, 1:5]
|
109 |
+
>>> X[1:9:2, 2:6][1:3, 2]
|
110 |
+
X[3:7:2, 4:5]
|
111 |
+
"""
|
112 |
+
row = slice_of_slice(parent.rowslice, rowslice)
|
113 |
+
col = slice_of_slice(parent.colslice, colslice)
|
114 |
+
return MatrixSlice(parent.parent, row, col)
|
.venv/lib/python3.11/site-packages/sympy/matrices/expressions/tests/__pycache__/test_applyfunc.cpython-311.pyc
ADDED
Binary file (8.73 kB). View file
|
|
.venv/lib/python3.11/site-packages/sympy/matrices/expressions/tests/__pycache__/test_derivatives.cpython-311.pyc
ADDED
Binary file (50.7 kB). View file
|
|
.venv/lib/python3.11/site-packages/sympy/matrices/expressions/tests/__pycache__/test_dotproduct.cpython-311.pyc
ADDED
Binary file (3.71 kB). View file
|
|