add read me
This commit is contained in:
271
venv/lib/python3.12/site-packages/sympy/tensor/array/__init__.py
Normal file
271
venv/lib/python3.12/site-packages/sympy/tensor/array/__init__.py
Normal file
@@ -0,0 +1,271 @@
|
||||
r"""
|
||||
N-dim array module for SymPy.
|
||||
|
||||
Four classes are provided to handle N-dim arrays, given by the combinations
|
||||
dense/sparse (i.e. whether to store all elements or only the non-zero ones in
|
||||
memory) and mutable/immutable (immutable classes are SymPy objects, but cannot
|
||||
change after they have been created).
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
The following examples show the usage of ``Array``. This is an abbreviation for
|
||||
``ImmutableDenseNDimArray``, that is an immutable and dense N-dim array, the
|
||||
other classes are analogous. For mutable classes it is also possible to change
|
||||
element values after the object has been constructed.
|
||||
|
||||
Array construction can detect the shape of nested lists and tuples:
|
||||
|
||||
>>> from sympy import Array
|
||||
>>> a1 = Array([[1, 2], [3, 4], [5, 6]])
|
||||
>>> a1
|
||||
[[1, 2], [3, 4], [5, 6]]
|
||||
>>> a1.shape
|
||||
(3, 2)
|
||||
>>> a1.rank()
|
||||
2
|
||||
>>> from sympy.abc import x, y, z
|
||||
>>> a2 = Array([[[x, y], [z, x*z]], [[1, x*y], [1/x, x/y]]])
|
||||
>>> a2
|
||||
[[[x, y], [z, x*z]], [[1, x*y], [1/x, x/y]]]
|
||||
>>> a2.shape
|
||||
(2, 2, 2)
|
||||
>>> a2.rank()
|
||||
3
|
||||
|
||||
Otherwise one could pass a 1-dim array followed by a shape tuple:
|
||||
|
||||
>>> m1 = Array(range(12), (3, 4))
|
||||
>>> m1
|
||||
[[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]]
|
||||
>>> m2 = Array(range(12), (3, 2, 2))
|
||||
>>> m2
|
||||
[[[0, 1], [2, 3]], [[4, 5], [6, 7]], [[8, 9], [10, 11]]]
|
||||
>>> m2[1,1,1]
|
||||
7
|
||||
>>> m2.reshape(4, 3)
|
||||
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11]]
|
||||
|
||||
Slice support:
|
||||
|
||||
>>> m2[:, 1, 1]
|
||||
[3, 7, 11]
|
||||
|
||||
Elementwise derivative:
|
||||
|
||||
>>> from sympy.abc import x, y, z
|
||||
>>> m3 = Array([x**3, x*y, z])
|
||||
>>> m3.diff(x)
|
||||
[3*x**2, y, 0]
|
||||
>>> m3.diff(z)
|
||||
[0, 0, 1]
|
||||
|
||||
Multiplication with other SymPy expressions is applied elementwisely:
|
||||
|
||||
>>> (1+x)*m3
|
||||
[x**3*(x + 1), x*y*(x + 1), z*(x + 1)]
|
||||
|
||||
To apply a function to each element of the N-dim array, use ``applyfunc``:
|
||||
|
||||
>>> m3.applyfunc(lambda x: x/2)
|
||||
[x**3/2, x*y/2, z/2]
|
||||
|
||||
N-dim arrays can be converted to nested lists by the ``tolist()`` method:
|
||||
|
||||
>>> m2.tolist()
|
||||
[[[0, 1], [2, 3]], [[4, 5], [6, 7]], [[8, 9], [10, 11]]]
|
||||
>>> isinstance(m2.tolist(), list)
|
||||
True
|
||||
|
||||
If the rank is 2, it is possible to convert them to matrices with ``tomatrix()``:
|
||||
|
||||
>>> m1.tomatrix()
|
||||
Matrix([
|
||||
[0, 1, 2, 3],
|
||||
[4, 5, 6, 7],
|
||||
[8, 9, 10, 11]])
|
||||
|
||||
Products and contractions
|
||||
-------------------------
|
||||
|
||||
Tensor product between arrays `A_{i_1,\ldots,i_n}` and `B_{j_1,\ldots,j_m}`
|
||||
creates the combined array `P = A \otimes B` defined as
|
||||
|
||||
`P_{i_1,\ldots,i_n,j_1,\ldots,j_m} := A_{i_1,\ldots,i_n}\cdot B_{j_1,\ldots,j_m}.`
|
||||
|
||||
It is available through ``tensorproduct(...)``:
|
||||
|
||||
>>> from sympy import Array, tensorproduct
|
||||
>>> from sympy.abc import x,y,z,t
|
||||
>>> A = Array([x, y, z, t])
|
||||
>>> B = Array([1, 2, 3, 4])
|
||||
>>> tensorproduct(A, B)
|
||||
[[x, 2*x, 3*x, 4*x], [y, 2*y, 3*y, 4*y], [z, 2*z, 3*z, 4*z], [t, 2*t, 3*t, 4*t]]
|
||||
|
||||
In case you don't want to evaluate the tensor product immediately, you can use
|
||||
``ArrayTensorProduct``, which creates an unevaluated tensor product expression:
|
||||
|
||||
>>> from sympy.tensor.array.expressions import ArrayTensorProduct
|
||||
>>> ArrayTensorProduct(A, B)
|
||||
ArrayTensorProduct([x, y, z, t], [1, 2, 3, 4])
|
||||
|
||||
Calling ``.as_explicit()`` on ``ArrayTensorProduct`` is equivalent to just calling
|
||||
``tensorproduct(...)``:
|
||||
|
||||
>>> ArrayTensorProduct(A, B).as_explicit()
|
||||
[[x, 2*x, 3*x, 4*x], [y, 2*y, 3*y, 4*y], [z, 2*z, 3*z, 4*z], [t, 2*t, 3*t, 4*t]]
|
||||
|
||||
Tensor product between a rank-1 array and a matrix creates a rank-3 array:
|
||||
|
||||
>>> from sympy import eye
|
||||
>>> p1 = tensorproduct(A, eye(4))
|
||||
>>> p1
|
||||
[[[x, 0, 0, 0], [0, x, 0, 0], [0, 0, x, 0], [0, 0, 0, x]], [[y, 0, 0, 0], [0, y, 0, 0], [0, 0, y, 0], [0, 0, 0, y]], [[z, 0, 0, 0], [0, z, 0, 0], [0, 0, z, 0], [0, 0, 0, z]], [[t, 0, 0, 0], [0, t, 0, 0], [0, 0, t, 0], [0, 0, 0, t]]]
|
||||
|
||||
Now, to get back `A_0 \otimes \mathbf{1}` one can access `p_{0,m,n}` by slicing:
|
||||
|
||||
>>> p1[0,:,:]
|
||||
[[x, 0, 0, 0], [0, x, 0, 0], [0, 0, x, 0], [0, 0, 0, x]]
|
||||
|
||||
Tensor contraction sums over the specified axes, for example contracting
|
||||
positions `a` and `b` means
|
||||
|
||||
`A_{i_1,\ldots,i_a,\ldots,i_b,\ldots,i_n} \implies \sum_k A_{i_1,\ldots,k,\ldots,k,\ldots,i_n}`
|
||||
|
||||
Remember that Python indexing is zero starting, to contract the a-th and b-th
|
||||
axes it is therefore necessary to specify `a-1` and `b-1`
|
||||
|
||||
>>> from sympy import tensorcontraction
|
||||
>>> C = Array([[x, y], [z, t]])
|
||||
|
||||
The matrix trace is equivalent to the contraction of a rank-2 array:
|
||||
|
||||
`A_{m,n} \implies \sum_k A_{k,k}`
|
||||
|
||||
>>> tensorcontraction(C, (0, 1))
|
||||
t + x
|
||||
|
||||
To create an expression representing a tensor contraction that does not get
|
||||
evaluated immediately, use ``ArrayContraction``, which is equivalent to
|
||||
``tensorcontraction(...)`` if it is followed by ``.as_explicit()``:
|
||||
|
||||
>>> from sympy.tensor.array.expressions import ArrayContraction
|
||||
>>> ArrayContraction(C, (0, 1))
|
||||
ArrayContraction([[x, y], [z, t]], (0, 1))
|
||||
>>> ArrayContraction(C, (0, 1)).as_explicit()
|
||||
t + x
|
||||
|
||||
Matrix product is equivalent to a tensor product of two rank-2 arrays, followed
|
||||
by a contraction of the 2nd and 3rd axes (in Python indexing axes number 1, 2).
|
||||
|
||||
`A_{m,n}\cdot B_{i,j} \implies \sum_k A_{m, k}\cdot B_{k, j}`
|
||||
|
||||
>>> D = Array([[2, 1], [0, -1]])
|
||||
>>> tensorcontraction(tensorproduct(C, D), (1, 2))
|
||||
[[2*x, x - y], [2*z, -t + z]]
|
||||
|
||||
One may verify that the matrix product is equivalent:
|
||||
|
||||
>>> from sympy import Matrix
|
||||
>>> Matrix([[x, y], [z, t]])*Matrix([[2, 1], [0, -1]])
|
||||
Matrix([
|
||||
[2*x, x - y],
|
||||
[2*z, -t + z]])
|
||||
|
||||
or equivalently
|
||||
|
||||
>>> C.tomatrix()*D.tomatrix()
|
||||
Matrix([
|
||||
[2*x, x - y],
|
||||
[2*z, -t + z]])
|
||||
|
||||
Diagonal operator
|
||||
-----------------
|
||||
|
||||
The ``tensordiagonal`` function acts in a similar manner as ``tensorcontraction``,
|
||||
but the joined indices are not summed over, for example diagonalizing
|
||||
positions `a` and `b` means
|
||||
|
||||
`A_{i_1,\ldots,i_a,\ldots,i_b,\ldots,i_n} \implies A_{i_1,\ldots,k,\ldots,k,\ldots,i_n}
|
||||
\implies \tilde{A}_{i_1,\ldots,i_{a-1},i_{a+1},\ldots,i_{b-1},i_{b+1},\ldots,i_n,k}`
|
||||
|
||||
where `\tilde{A}` is the array equivalent to the diagonal of `A` at positions
|
||||
`a` and `b` moved to the last index slot.
|
||||
|
||||
Compare the difference between contraction and diagonal operators:
|
||||
|
||||
>>> from sympy import tensordiagonal
|
||||
>>> from sympy.abc import a, b, c, d
|
||||
>>> m = Matrix([[a, b], [c, d]])
|
||||
>>> tensorcontraction(m, [0, 1])
|
||||
a + d
|
||||
>>> tensordiagonal(m, [0, 1])
|
||||
[a, d]
|
||||
|
||||
In short, no summation occurs with ``tensordiagonal``.
|
||||
|
||||
|
||||
Derivatives by array
|
||||
--------------------
|
||||
|
||||
The usual derivative operation may be extended to support derivation with
|
||||
respect to arrays, provided that all elements in the that array are symbols or
|
||||
expressions suitable for derivations.
|
||||
|
||||
The definition of a derivative by an array is as follows: given the array
|
||||
`A_{i_1, \ldots, i_N}` and the array `X_{j_1, \ldots, j_M}`
|
||||
the derivative of arrays will return a new array `B` defined by
|
||||
|
||||
`B_{j_1,\ldots,j_M,i_1,\ldots,i_N} := \frac{\partial A_{i_1,\ldots,i_N}}{\partial X_{j_1,\ldots,j_M}}`
|
||||
|
||||
The function ``derive_by_array`` performs such an operation:
|
||||
|
||||
>>> from sympy import derive_by_array
|
||||
>>> from sympy.abc import x, y, z, t
|
||||
>>> from sympy import sin, exp
|
||||
|
||||
With scalars, it behaves exactly as the ordinary derivative:
|
||||
|
||||
>>> derive_by_array(sin(x*y), x)
|
||||
y*cos(x*y)
|
||||
|
||||
Scalar derived by an array basis:
|
||||
|
||||
>>> derive_by_array(sin(x*y), [x, y, z])
|
||||
[y*cos(x*y), x*cos(x*y), 0]
|
||||
|
||||
Deriving array by an array basis: `B^{nm} := \frac{\partial A^m}{\partial x^n}`
|
||||
|
||||
>>> basis = [x, y, z]
|
||||
>>> ax = derive_by_array([exp(x), sin(y*z), t], basis)
|
||||
>>> ax
|
||||
[[exp(x), 0, 0], [0, z*cos(y*z), 0], [0, y*cos(y*z), 0]]
|
||||
|
||||
Contraction of the resulting array: `\sum_m \frac{\partial A^m}{\partial x^m}`
|
||||
|
||||
>>> tensorcontraction(ax, (0, 1))
|
||||
z*cos(y*z) + exp(x)
|
||||
|
||||
"""
|
||||
|
||||
from .dense_ndim_array import MutableDenseNDimArray, ImmutableDenseNDimArray, DenseNDimArray
|
||||
from .sparse_ndim_array import MutableSparseNDimArray, ImmutableSparseNDimArray, SparseNDimArray
|
||||
from .ndim_array import NDimArray, ArrayKind
|
||||
from .arrayop import tensorproduct, tensorcontraction, tensordiagonal, derive_by_array, permutedims
|
||||
from .array_comprehension import ArrayComprehension, ArrayComprehensionMap
|
||||
|
||||
Array = ImmutableDenseNDimArray
|
||||
|
||||
__all__ = [
|
||||
'MutableDenseNDimArray', 'ImmutableDenseNDimArray', 'DenseNDimArray',
|
||||
|
||||
'MutableSparseNDimArray', 'ImmutableSparseNDimArray', 'SparseNDimArray',
|
||||
|
||||
'NDimArray', 'ArrayKind',
|
||||
|
||||
'tensorproduct', 'tensorcontraction', 'tensordiagonal', 'derive_by_array',
|
||||
|
||||
'permutedims', 'ArrayComprehension', 'ArrayComprehensionMap',
|
||||
|
||||
'Array',
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,399 @@
|
||||
import functools, itertools
|
||||
from sympy.core.sympify import _sympify, sympify
|
||||
from sympy.core.expr import Expr
|
||||
from sympy.core import Basic, Tuple
|
||||
from sympy.tensor.array import ImmutableDenseNDimArray
|
||||
from sympy.core.symbol import Symbol
|
||||
from sympy.core.numbers import Integer
|
||||
|
||||
|
||||
class ArrayComprehension(Basic):
|
||||
"""
|
||||
Generate a list comprehension.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
If there is a symbolic dimension, for example, say [i for i in range(1, N)] where
|
||||
N is a Symbol, then the expression will not be expanded to an array. Otherwise,
|
||||
calling the doit() function will launch the expansion.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.tensor.array import ArrayComprehension
|
||||
>>> from sympy import symbols
|
||||
>>> i, j, k = symbols('i j k')
|
||||
>>> a = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, 3))
|
||||
>>> a
|
||||
ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, 3))
|
||||
>>> a.doit()
|
||||
[[11, 12, 13], [21, 22, 23], [31, 32, 33], [41, 42, 43]]
|
||||
>>> b = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, k))
|
||||
>>> b.doit()
|
||||
ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, k))
|
||||
"""
|
||||
def __new__(cls, function, *symbols, **assumptions):
|
||||
if any(len(l) != 3 or None for l in symbols):
|
||||
raise ValueError('ArrayComprehension requires values lower and upper bound'
|
||||
' for the expression')
|
||||
arglist = [sympify(function)]
|
||||
arglist.extend(cls._check_limits_validity(function, symbols))
|
||||
obj = Basic.__new__(cls, *arglist, **assumptions)
|
||||
obj._limits = obj._args[1:]
|
||||
obj._shape = cls._calculate_shape_from_limits(obj._limits)
|
||||
obj._rank = len(obj._shape)
|
||||
obj._loop_size = cls._calculate_loop_size(obj._shape)
|
||||
return obj
|
||||
|
||||
@property
|
||||
def function(self):
|
||||
"""The function applied across limits.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.tensor.array import ArrayComprehension
|
||||
>>> from sympy import symbols
|
||||
>>> i, j = symbols('i j')
|
||||
>>> a = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, 3))
|
||||
>>> a.function
|
||||
10*i + j
|
||||
"""
|
||||
return self._args[0]
|
||||
|
||||
@property
|
||||
def limits(self):
|
||||
"""
|
||||
The list of limits that will be applied while expanding the array.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.tensor.array import ArrayComprehension
|
||||
>>> from sympy import symbols
|
||||
>>> i, j = symbols('i j')
|
||||
>>> a = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, 3))
|
||||
>>> a.limits
|
||||
((i, 1, 4), (j, 1, 3))
|
||||
"""
|
||||
return self._limits
|
||||
|
||||
@property
|
||||
def free_symbols(self):
|
||||
"""
|
||||
The set of the free_symbols in the array.
|
||||
Variables appeared in the bounds are supposed to be excluded
|
||||
from the free symbol set.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.tensor.array import ArrayComprehension
|
||||
>>> from sympy import symbols
|
||||
>>> i, j, k = symbols('i j k')
|
||||
>>> a = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, 3))
|
||||
>>> a.free_symbols
|
||||
set()
|
||||
>>> b = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, k+3))
|
||||
>>> b.free_symbols
|
||||
{k}
|
||||
"""
|
||||
expr_free_sym = self.function.free_symbols
|
||||
for var, inf, sup in self._limits:
|
||||
expr_free_sym.discard(var)
|
||||
curr_free_syms = inf.free_symbols.union(sup.free_symbols)
|
||||
expr_free_sym = expr_free_sym.union(curr_free_syms)
|
||||
return expr_free_sym
|
||||
|
||||
@property
|
||||
def variables(self):
|
||||
"""The tuples of the variables in the limits.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.tensor.array import ArrayComprehension
|
||||
>>> from sympy import symbols
|
||||
>>> i, j, k = symbols('i j k')
|
||||
>>> a = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, 3))
|
||||
>>> a.variables
|
||||
[i, j]
|
||||
"""
|
||||
return [l[0] for l in self._limits]
|
||||
|
||||
@property
|
||||
def bound_symbols(self):
|
||||
"""The list of dummy variables.
|
||||
|
||||
Note
|
||||
====
|
||||
|
||||
Note that all variables are dummy variables since a limit without
|
||||
lower bound or upper bound is not accepted.
|
||||
"""
|
||||
return [l[0] for l in self._limits if len(l) != 1]
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
"""
|
||||
The shape of the expanded array, which may have symbols.
|
||||
|
||||
Note
|
||||
====
|
||||
|
||||
Both the lower and the upper bounds are included while
|
||||
calculating the shape.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.tensor.array import ArrayComprehension
|
||||
>>> from sympy import symbols
|
||||
>>> i, j, k = symbols('i j k')
|
||||
>>> a = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, 3))
|
||||
>>> a.shape
|
||||
(4, 3)
|
||||
>>> b = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, k+3))
|
||||
>>> b.shape
|
||||
(4, k + 3)
|
||||
"""
|
||||
return self._shape
|
||||
|
||||
@property
|
||||
def is_shape_numeric(self):
|
||||
"""
|
||||
Test if the array is shape-numeric which means there is no symbolic
|
||||
dimension.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.tensor.array import ArrayComprehension
|
||||
>>> from sympy import symbols
|
||||
>>> i, j, k = symbols('i j k')
|
||||
>>> a = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, 3))
|
||||
>>> a.is_shape_numeric
|
||||
True
|
||||
>>> b = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, k+3))
|
||||
>>> b.is_shape_numeric
|
||||
False
|
||||
"""
|
||||
for _, inf, sup in self._limits:
|
||||
if Basic(inf, sup).atoms(Symbol):
|
||||
return False
|
||||
return True
|
||||
|
||||
def rank(self):
|
||||
"""The rank of the expanded array.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.tensor.array import ArrayComprehension
|
||||
>>> from sympy import symbols
|
||||
>>> i, j, k = symbols('i j k')
|
||||
>>> a = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, 3))
|
||||
>>> a.rank()
|
||||
2
|
||||
"""
|
||||
return self._rank
|
||||
|
||||
def __len__(self):
|
||||
"""
|
||||
The length of the expanded array which means the number
|
||||
of elements in the array.
|
||||
|
||||
Raises
|
||||
======
|
||||
|
||||
ValueError : When the length of the array is symbolic
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.tensor.array import ArrayComprehension
|
||||
>>> from sympy import symbols
|
||||
>>> i, j = symbols('i j')
|
||||
>>> a = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, 3))
|
||||
>>> len(a)
|
||||
12
|
||||
"""
|
||||
if self._loop_size.free_symbols:
|
||||
raise ValueError('Symbolic length is not supported')
|
||||
return self._loop_size
|
||||
|
||||
@classmethod
|
||||
def _check_limits_validity(cls, function, limits):
|
||||
#limits = sympify(limits)
|
||||
new_limits = []
|
||||
for var, inf, sup in limits:
|
||||
var = _sympify(var)
|
||||
inf = _sympify(inf)
|
||||
#since this is stored as an argument, it should be
|
||||
#a Tuple
|
||||
if isinstance(sup, list):
|
||||
sup = Tuple(*sup)
|
||||
else:
|
||||
sup = _sympify(sup)
|
||||
new_limits.append(Tuple(var, inf, sup))
|
||||
if any((not isinstance(i, Expr)) or i.atoms(Symbol, Integer) != i.atoms()
|
||||
for i in [inf, sup]):
|
||||
raise TypeError('Bounds should be an Expression(combination of Integer and Symbol)')
|
||||
if (inf > sup) == True:
|
||||
raise ValueError('Lower bound should be inferior to upper bound')
|
||||
if var in inf.free_symbols or var in sup.free_symbols:
|
||||
raise ValueError('Variable should not be part of its bounds')
|
||||
return new_limits
|
||||
|
||||
@classmethod
|
||||
def _calculate_shape_from_limits(cls, limits):
|
||||
return tuple([sup - inf + 1 for _, inf, sup in limits])
|
||||
|
||||
@classmethod
|
||||
def _calculate_loop_size(cls, shape):
|
||||
if not shape:
|
||||
return 0
|
||||
loop_size = 1
|
||||
for l in shape:
|
||||
loop_size = loop_size * l
|
||||
|
||||
return loop_size
|
||||
|
||||
def doit(self, **hints):
|
||||
if not self.is_shape_numeric:
|
||||
return self
|
||||
|
||||
return self._expand_array()
|
||||
|
||||
def _expand_array(self):
|
||||
res = []
|
||||
for values in itertools.product(*[range(inf, sup+1)
|
||||
for var, inf, sup
|
||||
in self._limits]):
|
||||
res.append(self._get_element(values))
|
||||
|
||||
return ImmutableDenseNDimArray(res, self.shape)
|
||||
|
||||
def _get_element(self, values):
|
||||
temp = self.function
|
||||
for var, val in zip(self.variables, values):
|
||||
temp = temp.subs(var, val)
|
||||
return temp
|
||||
|
||||
def tolist(self):
|
||||
"""Transform the expanded array to a list.
|
||||
|
||||
Raises
|
||||
======
|
||||
|
||||
ValueError : When there is a symbolic dimension
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.tensor.array import ArrayComprehension
|
||||
>>> from sympy import symbols
|
||||
>>> i, j = symbols('i j')
|
||||
>>> a = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, 3))
|
||||
>>> a.tolist()
|
||||
[[11, 12, 13], [21, 22, 23], [31, 32, 33], [41, 42, 43]]
|
||||
"""
|
||||
if self.is_shape_numeric:
|
||||
return self._expand_array().tolist()
|
||||
|
||||
raise ValueError("A symbolic array cannot be expanded to a list")
|
||||
|
||||
def tomatrix(self):
|
||||
"""Transform the expanded array to a matrix.
|
||||
|
||||
Raises
|
||||
======
|
||||
|
||||
ValueError : When there is a symbolic dimension
|
||||
ValueError : When the rank of the expanded array is not equal to 2
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.tensor.array import ArrayComprehension
|
||||
>>> from sympy import symbols
|
||||
>>> i, j = symbols('i j')
|
||||
>>> a = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, 3))
|
||||
>>> a.tomatrix()
|
||||
Matrix([
|
||||
[11, 12, 13],
|
||||
[21, 22, 23],
|
||||
[31, 32, 33],
|
||||
[41, 42, 43]])
|
||||
"""
|
||||
from sympy.matrices import Matrix
|
||||
|
||||
if not self.is_shape_numeric:
|
||||
raise ValueError("A symbolic array cannot be expanded to a matrix")
|
||||
if self._rank != 2:
|
||||
raise ValueError('Dimensions must be of size of 2')
|
||||
|
||||
return Matrix(self._expand_array().tomatrix())
|
||||
|
||||
|
||||
def isLambda(v):
|
||||
LAMBDA = lambda: 0
|
||||
return isinstance(v, type(LAMBDA)) and v.__name__ == LAMBDA.__name__
|
||||
|
||||
class ArrayComprehensionMap(ArrayComprehension):
|
||||
'''
|
||||
A subclass of ArrayComprehension dedicated to map external function lambda.
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
Only the lambda function is considered.
|
||||
At most one argument in lambda function is accepted in order to avoid ambiguity
|
||||
in value assignment.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.tensor.array import ArrayComprehensionMap
|
||||
>>> from sympy import symbols
|
||||
>>> i, j, k = symbols('i j k')
|
||||
>>> a = ArrayComprehensionMap(lambda: 1, (i, 1, 4))
|
||||
>>> a.doit()
|
||||
[1, 1, 1, 1]
|
||||
>>> b = ArrayComprehensionMap(lambda a: a+1, (j, 1, 4))
|
||||
>>> b.doit()
|
||||
[2, 3, 4, 5]
|
||||
|
||||
'''
|
||||
def __new__(cls, function, *symbols, **assumptions):
|
||||
if any(len(l) != 3 or None for l in symbols):
|
||||
raise ValueError('ArrayComprehension requires values lower and upper bound'
|
||||
' for the expression')
|
||||
|
||||
if not isLambda(function):
|
||||
raise ValueError('Data type not supported')
|
||||
|
||||
arglist = cls._check_limits_validity(function, symbols)
|
||||
obj = Basic.__new__(cls, *arglist, **assumptions)
|
||||
obj._limits = obj._args
|
||||
obj._shape = cls._calculate_shape_from_limits(obj._limits)
|
||||
obj._rank = len(obj._shape)
|
||||
obj._loop_size = cls._calculate_loop_size(obj._shape)
|
||||
obj._lambda = function
|
||||
return obj
|
||||
|
||||
@property
|
||||
def func(self):
|
||||
class _(ArrayComprehensionMap):
|
||||
def __new__(cls, *args, **kwargs):
|
||||
return ArrayComprehensionMap(self._lambda, *args, **kwargs)
|
||||
return _
|
||||
|
||||
def _get_element(self, values):
|
||||
temp = self._lambda
|
||||
if self._lambda.__code__.co_argcount == 0:
|
||||
temp = temp()
|
||||
elif self._lambda.__code__.co_argcount == 1:
|
||||
temp = temp(functools.reduce(lambda a, b: a*b, values))
|
||||
return temp
|
||||
@@ -0,0 +1,129 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from sympy.core.expr import Expr
|
||||
from sympy.core.function import Derivative
|
||||
from sympy.core.numbers import Integer
|
||||
from sympy.matrices.matrixbase import MatrixBase
|
||||
from .ndim_array import NDimArray
|
||||
from .arrayop import derive_by_array
|
||||
from sympy.matrices.expressions.matexpr import MatrixExpr
|
||||
from sympy.matrices.expressions.special import ZeroMatrix
|
||||
from sympy.matrices.expressions.matexpr import _matrix_derivative
|
||||
|
||||
|
||||
class ArrayDerivative(Derivative):
|
||||
|
||||
is_scalar = False
|
||||
|
||||
def __new__(cls, expr, *variables, **kwargs):
|
||||
obj = super().__new__(cls, expr, *variables, **kwargs)
|
||||
if isinstance(obj, ArrayDerivative):
|
||||
obj._shape = obj._get_shape()
|
||||
return obj
|
||||
|
||||
def _get_shape(self):
|
||||
shape = ()
|
||||
for v, count in self.variable_count:
|
||||
if hasattr(v, "shape"):
|
||||
for i in range(count):
|
||||
shape += v.shape
|
||||
if hasattr(self.expr, "shape"):
|
||||
shape += self.expr.shape
|
||||
return shape
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
return self._shape
|
||||
|
||||
@classmethod
|
||||
def _get_zero_with_shape_like(cls, expr):
|
||||
if isinstance(expr, (MatrixBase, NDimArray)):
|
||||
return expr.zeros(*expr.shape)
|
||||
elif isinstance(expr, MatrixExpr):
|
||||
return ZeroMatrix(*expr.shape)
|
||||
else:
|
||||
raise RuntimeError("Unable to determine shape of array-derivative.")
|
||||
|
||||
@staticmethod
|
||||
def _call_derive_scalar_by_matrix(expr: Expr, v: MatrixBase) -> Expr:
|
||||
return v.applyfunc(lambda x: expr.diff(x))
|
||||
|
||||
@staticmethod
|
||||
def _call_derive_scalar_by_matexpr(expr: Expr, v: MatrixExpr) -> Expr:
|
||||
if expr.has(v):
|
||||
return _matrix_derivative(expr, v)
|
||||
else:
|
||||
return ZeroMatrix(*v.shape)
|
||||
|
||||
@staticmethod
|
||||
def _call_derive_scalar_by_array(expr: Expr, v: NDimArray) -> Expr:
|
||||
return v.applyfunc(lambda x: expr.diff(x))
|
||||
|
||||
@staticmethod
|
||||
def _call_derive_matrix_by_scalar(expr: MatrixBase, v: Expr) -> Expr:
|
||||
return _matrix_derivative(expr, v)
|
||||
|
||||
@staticmethod
|
||||
def _call_derive_matexpr_by_scalar(expr: MatrixExpr, v: Expr) -> Expr:
|
||||
return expr._eval_derivative(v)
|
||||
|
||||
@staticmethod
|
||||
def _call_derive_array_by_scalar(expr: NDimArray, v: Expr) -> Expr:
|
||||
return expr.applyfunc(lambda x: x.diff(v))
|
||||
|
||||
@staticmethod
|
||||
def _call_derive_default(expr: Expr, v: Expr) -> Expr | None:
|
||||
if expr.has(v):
|
||||
return _matrix_derivative(expr, v)
|
||||
else:
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def _dispatch_eval_derivative_n_times(cls, expr, v, count):
|
||||
# Evaluate the derivative `n` times. If
|
||||
# `_eval_derivative_n_times` is not overridden by the current
|
||||
# object, the default in `Basic` will call a loop over
|
||||
# `_eval_derivative`:
|
||||
|
||||
if not isinstance(count, (int, Integer)) or ((count <= 0) == True):
|
||||
return None
|
||||
|
||||
# TODO: this could be done with multiple-dispatching:
|
||||
if expr.is_scalar:
|
||||
if isinstance(v, MatrixBase):
|
||||
result = cls._call_derive_scalar_by_matrix(expr, v)
|
||||
elif isinstance(v, MatrixExpr):
|
||||
result = cls._call_derive_scalar_by_matexpr(expr, v)
|
||||
elif isinstance(v, NDimArray):
|
||||
result = cls._call_derive_scalar_by_array(expr, v)
|
||||
elif v.is_scalar:
|
||||
# scalar by scalar has a special
|
||||
return super()._dispatch_eval_derivative_n_times(expr, v, count)
|
||||
else:
|
||||
return None
|
||||
elif v.is_scalar:
|
||||
if isinstance(expr, MatrixBase):
|
||||
result = cls._call_derive_matrix_by_scalar(expr, v)
|
||||
elif isinstance(expr, MatrixExpr):
|
||||
result = cls._call_derive_matexpr_by_scalar(expr, v)
|
||||
elif isinstance(expr, NDimArray):
|
||||
result = cls._call_derive_array_by_scalar(expr, v)
|
||||
else:
|
||||
return None
|
||||
else:
|
||||
# Both `expr` and `v` are some array/matrix type:
|
||||
if isinstance(expr, MatrixBase) or isinstance(v, MatrixBase):
|
||||
result = derive_by_array(expr, v)
|
||||
elif isinstance(expr, MatrixExpr) and isinstance(v, MatrixExpr):
|
||||
result = cls._call_derive_default(expr, v)
|
||||
elif isinstance(expr, MatrixExpr) or isinstance(v, MatrixExpr):
|
||||
# if one expression is a symbolic matrix expression while the other isn't, don't evaluate:
|
||||
return None
|
||||
else:
|
||||
result = derive_by_array(expr, v)
|
||||
if result is None:
|
||||
return None
|
||||
if count == 1:
|
||||
return result
|
||||
else:
|
||||
return cls._dispatch_eval_derivative_n_times(result, v, count - 1)
|
||||
528
venv/lib/python3.12/site-packages/sympy/tensor/array/arrayop.py
Normal file
528
venv/lib/python3.12/site-packages/sympy/tensor/array/arrayop.py
Normal file
@@ -0,0 +1,528 @@
|
||||
import itertools
|
||||
from collections.abc import Iterable
|
||||
|
||||
from sympy.core._print_helpers import Printable
|
||||
from sympy.core.containers import Tuple
|
||||
from sympy.core.function import diff
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.sympify import _sympify
|
||||
|
||||
from sympy.tensor.array.ndim_array import NDimArray
|
||||
from sympy.tensor.array.dense_ndim_array import DenseNDimArray, ImmutableDenseNDimArray
|
||||
from sympy.tensor.array.sparse_ndim_array import SparseNDimArray
|
||||
|
||||
|
||||
def _arrayfy(a):
|
||||
from sympy.matrices import MatrixBase
|
||||
|
||||
if isinstance(a, NDimArray):
|
||||
return a
|
||||
if isinstance(a, (MatrixBase, list, tuple, Tuple)):
|
||||
return ImmutableDenseNDimArray(a)
|
||||
return a
|
||||
|
||||
|
||||
def tensorproduct(*args):
|
||||
"""
|
||||
Tensor product among scalars or array-like objects.
|
||||
|
||||
The equivalent operator for array expressions is ``ArrayTensorProduct``,
|
||||
which can be used to keep the expression unevaluated.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.tensor.array import tensorproduct, Array
|
||||
>>> from sympy.abc import x, y, z, t
|
||||
>>> A = Array([[1, 2], [3, 4]])
|
||||
>>> B = Array([x, y])
|
||||
>>> tensorproduct(A, B)
|
||||
[[[x, y], [2*x, 2*y]], [[3*x, 3*y], [4*x, 4*y]]]
|
||||
>>> tensorproduct(A, x)
|
||||
[[x, 2*x], [3*x, 4*x]]
|
||||
>>> tensorproduct(A, B, B)
|
||||
[[[[x**2, x*y], [x*y, y**2]], [[2*x**2, 2*x*y], [2*x*y, 2*y**2]]], [[[3*x**2, 3*x*y], [3*x*y, 3*y**2]], [[4*x**2, 4*x*y], [4*x*y, 4*y**2]]]]
|
||||
|
||||
Applying this function on two matrices will result in a rank 4 array.
|
||||
|
||||
>>> from sympy import Matrix, eye
|
||||
>>> m = Matrix([[x, y], [z, t]])
|
||||
>>> p = tensorproduct(eye(3), m)
|
||||
>>> p
|
||||
[[[[x, y], [z, t]], [[0, 0], [0, 0]], [[0, 0], [0, 0]]], [[[0, 0], [0, 0]], [[x, y], [z, t]], [[0, 0], [0, 0]]], [[[0, 0], [0, 0]], [[0, 0], [0, 0]], [[x, y], [z, t]]]]
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.tensor.array.expressions.array_expressions.ArrayTensorProduct
|
||||
|
||||
"""
|
||||
from sympy.tensor.array import SparseNDimArray, ImmutableSparseNDimArray
|
||||
|
||||
if len(args) == 0:
|
||||
return S.One
|
||||
if len(args) == 1:
|
||||
return _arrayfy(args[0])
|
||||
from sympy.tensor.array.expressions.array_expressions import _CodegenArrayAbstract
|
||||
from sympy.tensor.array.expressions.array_expressions import ArrayTensorProduct
|
||||
from sympy.tensor.array.expressions.array_expressions import _ArrayExpr
|
||||
from sympy.matrices.expressions.matexpr import MatrixSymbol
|
||||
if any(isinstance(arg, (_ArrayExpr, _CodegenArrayAbstract, MatrixSymbol)) for arg in args):
|
||||
return ArrayTensorProduct(*args)
|
||||
if len(args) > 2:
|
||||
return tensorproduct(tensorproduct(args[0], args[1]), *args[2:])
|
||||
|
||||
# length of args is 2:
|
||||
a, b = map(_arrayfy, args)
|
||||
|
||||
if not isinstance(a, NDimArray) or not isinstance(b, NDimArray):
|
||||
return a*b
|
||||
|
||||
if isinstance(a, SparseNDimArray) and isinstance(b, SparseNDimArray):
|
||||
lp = len(b)
|
||||
new_array = {k1*lp + k2: v1*v2 for k1, v1 in a._sparse_array.items() for k2, v2 in b._sparse_array.items()}
|
||||
return ImmutableSparseNDimArray(new_array, a.shape + b.shape)
|
||||
|
||||
product_list = [i*j for i in Flatten(a) for j in Flatten(b)]
|
||||
return ImmutableDenseNDimArray(product_list, a.shape + b.shape)
|
||||
|
||||
|
||||
def _util_contraction_diagonal(array, *contraction_or_diagonal_axes):
|
||||
array = _arrayfy(array)
|
||||
|
||||
# Verify contraction_axes:
|
||||
taken_dims = set()
|
||||
for axes_group in contraction_or_diagonal_axes:
|
||||
if not isinstance(axes_group, Iterable):
|
||||
raise ValueError("collections of contraction/diagonal axes expected")
|
||||
|
||||
dim = array.shape[axes_group[0]]
|
||||
|
||||
for d in axes_group:
|
||||
if d in taken_dims:
|
||||
raise ValueError("dimension specified more than once")
|
||||
if dim != array.shape[d]:
|
||||
raise ValueError("cannot contract or diagonalize between axes of different dimension")
|
||||
taken_dims.add(d)
|
||||
|
||||
rank = array.rank()
|
||||
|
||||
remaining_shape = [dim for i, dim in enumerate(array.shape) if i not in taken_dims]
|
||||
cum_shape = [0]*rank
|
||||
_cumul = 1
|
||||
for i in range(rank):
|
||||
cum_shape[rank - i - 1] = _cumul
|
||||
_cumul *= int(array.shape[rank - i - 1])
|
||||
|
||||
# DEFINITION: by absolute position it is meant the position along the one
|
||||
# dimensional array containing all the tensor components.
|
||||
|
||||
# Possible future work on this module: move computation of absolute
|
||||
# positions to a class method.
|
||||
|
||||
# Determine absolute positions of the uncontracted indices:
|
||||
remaining_indices = [[cum_shape[i]*j for j in range(array.shape[i])]
|
||||
for i in range(rank) if i not in taken_dims]
|
||||
|
||||
# Determine absolute positions of the contracted indices:
|
||||
summed_deltas = []
|
||||
for axes_group in contraction_or_diagonal_axes:
|
||||
lidx = []
|
||||
for js in range(array.shape[axes_group[0]]):
|
||||
lidx.append(sum(cum_shape[ig] * js for ig in axes_group))
|
||||
summed_deltas.append(lidx)
|
||||
|
||||
return array, remaining_indices, remaining_shape, summed_deltas
|
||||
|
||||
|
||||
def tensorcontraction(array, *contraction_axes):
|
||||
"""
|
||||
Contraction of an array-like object on the specified axes.
|
||||
|
||||
The equivalent operator for array expressions is ``ArrayContraction``,
|
||||
which can be used to keep the expression unevaluated.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Array, tensorcontraction
|
||||
>>> from sympy import Matrix, eye
|
||||
>>> tensorcontraction(eye(3), (0, 1))
|
||||
3
|
||||
>>> A = Array(range(18), (3, 2, 3))
|
||||
>>> A
|
||||
[[[0, 1, 2], [3, 4, 5]], [[6, 7, 8], [9, 10, 11]], [[12, 13, 14], [15, 16, 17]]]
|
||||
>>> tensorcontraction(A, (0, 2))
|
||||
[21, 30]
|
||||
|
||||
Matrix multiplication may be emulated with a proper combination of
|
||||
``tensorcontraction`` and ``tensorproduct``
|
||||
|
||||
>>> from sympy import tensorproduct
|
||||
>>> from sympy.abc import a,b,c,d,e,f,g,h
|
||||
>>> m1 = Matrix([[a, b], [c, d]])
|
||||
>>> m2 = Matrix([[e, f], [g, h]])
|
||||
>>> p = tensorproduct(m1, m2)
|
||||
>>> p
|
||||
[[[[a*e, a*f], [a*g, a*h]], [[b*e, b*f], [b*g, b*h]]], [[[c*e, c*f], [c*g, c*h]], [[d*e, d*f], [d*g, d*h]]]]
|
||||
>>> tensorcontraction(p, (1, 2))
|
||||
[[a*e + b*g, a*f + b*h], [c*e + d*g, c*f + d*h]]
|
||||
>>> m1*m2
|
||||
Matrix([
|
||||
[a*e + b*g, a*f + b*h],
|
||||
[c*e + d*g, c*f + d*h]])
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.tensor.array.expressions.array_expressions.ArrayContraction
|
||||
|
||||
"""
|
||||
from sympy.tensor.array.expressions.array_expressions import _array_contraction
|
||||
from sympy.tensor.array.expressions.array_expressions import _CodegenArrayAbstract
|
||||
from sympy.tensor.array.expressions.array_expressions import _ArrayExpr
|
||||
from sympy.matrices.expressions.matexpr import MatrixSymbol
|
||||
if isinstance(array, (_ArrayExpr, _CodegenArrayAbstract, MatrixSymbol)):
|
||||
return _array_contraction(array, *contraction_axes)
|
||||
|
||||
array, remaining_indices, remaining_shape, summed_deltas = _util_contraction_diagonal(array, *contraction_axes)
|
||||
|
||||
# Compute the contracted array:
|
||||
#
|
||||
# 1. external for loops on all uncontracted indices.
|
||||
# Uncontracted indices are determined by the combinatorial product of
|
||||
# the absolute positions of the remaining indices.
|
||||
# 2. internal loop on all contracted indices.
|
||||
# It sums the values of the absolute contracted index and the absolute
|
||||
# uncontracted index for the external loop.
|
||||
contracted_array = []
|
||||
for icontrib in itertools.product(*remaining_indices):
|
||||
index_base_position = sum(icontrib)
|
||||
isum = S.Zero
|
||||
for sum_to_index in itertools.product(*summed_deltas):
|
||||
idx = array._get_tuple_index(index_base_position + sum(sum_to_index))
|
||||
isum += array[idx]
|
||||
|
||||
contracted_array.append(isum)
|
||||
|
||||
if len(remaining_indices) == 0:
|
||||
assert len(contracted_array) == 1
|
||||
return contracted_array[0]
|
||||
|
||||
return type(array)(contracted_array, remaining_shape)
|
||||
|
||||
|
||||
def tensordiagonal(array, *diagonal_axes):
|
||||
"""
|
||||
Diagonalization of an array-like object on the specified axes.
|
||||
|
||||
This is equivalent to multiplying the expression by Kronecker deltas
|
||||
uniting the axes.
|
||||
|
||||
The diagonal indices are put at the end of the axes.
|
||||
|
||||
The equivalent operator for array expressions is ``ArrayDiagonal``, which
|
||||
can be used to keep the expression unevaluated.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
``tensordiagonal`` acting on a 2-dimensional array by axes 0 and 1 is
|
||||
equivalent to the diagonal of the matrix:
|
||||
|
||||
>>> from sympy import Array, tensordiagonal
|
||||
>>> from sympy import Matrix, eye
|
||||
>>> tensordiagonal(eye(3), (0, 1))
|
||||
[1, 1, 1]
|
||||
|
||||
>>> from sympy.abc import a,b,c,d
|
||||
>>> m1 = Matrix([[a, b], [c, d]])
|
||||
>>> tensordiagonal(m1, [0, 1])
|
||||
[a, d]
|
||||
|
||||
In case of higher dimensional arrays, the diagonalized out dimensions
|
||||
are appended removed and appended as a single dimension at the end:
|
||||
|
||||
>>> A = Array(range(18), (3, 2, 3))
|
||||
>>> A
|
||||
[[[0, 1, 2], [3, 4, 5]], [[6, 7, 8], [9, 10, 11]], [[12, 13, 14], [15, 16, 17]]]
|
||||
>>> tensordiagonal(A, (0, 2))
|
||||
[[0, 7, 14], [3, 10, 17]]
|
||||
>>> from sympy import permutedims
|
||||
>>> tensordiagonal(A, (0, 2)) == permutedims(Array([A[0, :, 0], A[1, :, 1], A[2, :, 2]]), [1, 0])
|
||||
True
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.tensor.array.expressions.array_expressions.ArrayDiagonal
|
||||
|
||||
"""
|
||||
if any(len(i) <= 1 for i in diagonal_axes):
|
||||
raise ValueError("need at least two axes to diagonalize")
|
||||
|
||||
from sympy.tensor.array.expressions.array_expressions import _ArrayExpr
|
||||
from sympy.tensor.array.expressions.array_expressions import _CodegenArrayAbstract
|
||||
from sympy.tensor.array.expressions.array_expressions import ArrayDiagonal, _array_diagonal
|
||||
from sympy.matrices.expressions.matexpr import MatrixSymbol
|
||||
if isinstance(array, (_ArrayExpr, _CodegenArrayAbstract, MatrixSymbol)):
|
||||
return _array_diagonal(array, *diagonal_axes)
|
||||
|
||||
ArrayDiagonal._validate(array, *diagonal_axes)
|
||||
|
||||
array, remaining_indices, remaining_shape, diagonal_deltas = _util_contraction_diagonal(array, *diagonal_axes)
|
||||
|
||||
# Compute the diagonalized array:
|
||||
#
|
||||
# 1. external for loops on all undiagonalized indices.
|
||||
# Undiagonalized indices are determined by the combinatorial product of
|
||||
# the absolute positions of the remaining indices.
|
||||
# 2. internal loop on all diagonal indices.
|
||||
# It appends the values of the absolute diagonalized index and the absolute
|
||||
# undiagonalized index for the external loop.
|
||||
diagonalized_array = []
|
||||
diagonal_shape = [len(i) for i in diagonal_deltas]
|
||||
for icontrib in itertools.product(*remaining_indices):
|
||||
index_base_position = sum(icontrib)
|
||||
isum = []
|
||||
for sum_to_index in itertools.product(*diagonal_deltas):
|
||||
idx = array._get_tuple_index(index_base_position + sum(sum_to_index))
|
||||
isum.append(array[idx])
|
||||
|
||||
isum = type(array)(isum).reshape(*diagonal_shape)
|
||||
diagonalized_array.append(isum)
|
||||
|
||||
return type(array)(diagonalized_array, remaining_shape + diagonal_shape)
|
||||
|
||||
|
||||
def derive_by_array(expr, dx):
|
||||
r"""
|
||||
Derivative by arrays. Supports both arrays and scalars.
|
||||
|
||||
The equivalent operator for array expressions is ``array_derive``.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
Given the array `A_{i_1, \ldots, i_N}` and the array `X_{j_1, \ldots, j_M}`
|
||||
this function will return a new array `B` defined by
|
||||
|
||||
`B_{j_1,\ldots,j_M,i_1,\ldots,i_N} := \frac{\partial A_{i_1,\ldots,i_N}}{\partial X_{j_1,\ldots,j_M}}`
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import derive_by_array
|
||||
>>> from sympy.abc import x, y, z, t
|
||||
>>> from sympy import cos
|
||||
>>> derive_by_array(cos(x*t), x)
|
||||
-t*sin(t*x)
|
||||
>>> derive_by_array(cos(x*t), [x, y, z, t])
|
||||
[-t*sin(t*x), 0, 0, -x*sin(t*x)]
|
||||
>>> derive_by_array([x, y**2*z], [[x, y], [z, t]])
|
||||
[[[1, 0], [0, 2*y*z]], [[0, y**2], [0, 0]]]
|
||||
|
||||
"""
|
||||
from sympy.matrices import MatrixBase
|
||||
from sympy.tensor.array import SparseNDimArray
|
||||
array_types = (Iterable, MatrixBase, NDimArray)
|
||||
|
||||
if isinstance(dx, array_types):
|
||||
dx = ImmutableDenseNDimArray(dx)
|
||||
for i in dx:
|
||||
if not i._diff_wrt:
|
||||
raise ValueError("cannot derive by this array")
|
||||
|
||||
if isinstance(expr, array_types):
|
||||
if isinstance(expr, NDimArray):
|
||||
expr = expr.as_immutable()
|
||||
else:
|
||||
expr = ImmutableDenseNDimArray(expr)
|
||||
|
||||
if isinstance(dx, array_types):
|
||||
if isinstance(expr, SparseNDimArray):
|
||||
lp = len(expr)
|
||||
new_array = {k + i*lp: v
|
||||
for i, x in enumerate(Flatten(dx))
|
||||
for k, v in expr.diff(x)._sparse_array.items()}
|
||||
else:
|
||||
new_array = [[y.diff(x) for y in Flatten(expr)] for x in Flatten(dx)]
|
||||
return type(expr)(new_array, dx.shape + expr.shape)
|
||||
else:
|
||||
return expr.diff(dx)
|
||||
else:
|
||||
expr = _sympify(expr)
|
||||
if isinstance(dx, array_types):
|
||||
return ImmutableDenseNDimArray([expr.diff(i) for i in Flatten(dx)], dx.shape)
|
||||
else:
|
||||
dx = _sympify(dx)
|
||||
return diff(expr, dx)
|
||||
|
||||
|
||||
def permutedims(expr, perm=None, index_order_old=None, index_order_new=None):
|
||||
"""
|
||||
Permutes the indices of an array.
|
||||
|
||||
Parameter specifies the permutation of the indices.
|
||||
|
||||
The equivalent operator for array expressions is ``PermuteDims``, which can
|
||||
be used to keep the expression unevaluated.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.abc import x, y, z, t
|
||||
>>> from sympy import sin
|
||||
>>> from sympy import Array, permutedims
|
||||
>>> a = Array([[x, y, z], [t, sin(x), 0]])
|
||||
>>> a
|
||||
[[x, y, z], [t, sin(x), 0]]
|
||||
>>> permutedims(a, (1, 0))
|
||||
[[x, t], [y, sin(x)], [z, 0]]
|
||||
|
||||
If the array is of second order, ``transpose`` can be used:
|
||||
|
||||
>>> from sympy import transpose
|
||||
>>> transpose(a)
|
||||
[[x, t], [y, sin(x)], [z, 0]]
|
||||
|
||||
Examples on higher dimensions:
|
||||
|
||||
>>> b = Array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])
|
||||
>>> permutedims(b, (2, 1, 0))
|
||||
[[[1, 5], [3, 7]], [[2, 6], [4, 8]]]
|
||||
>>> permutedims(b, (1, 2, 0))
|
||||
[[[1, 5], [2, 6]], [[3, 7], [4, 8]]]
|
||||
|
||||
An alternative way to specify the same permutations as in the previous
|
||||
lines involves passing the *old* and *new* indices, either as a list or as
|
||||
a string:
|
||||
|
||||
>>> permutedims(b, index_order_old="cba", index_order_new="abc")
|
||||
[[[1, 5], [3, 7]], [[2, 6], [4, 8]]]
|
||||
>>> permutedims(b, index_order_old="cab", index_order_new="abc")
|
||||
[[[1, 5], [2, 6]], [[3, 7], [4, 8]]]
|
||||
|
||||
``Permutation`` objects are also allowed:
|
||||
|
||||
>>> from sympy.combinatorics import Permutation
|
||||
>>> permutedims(b, Permutation([1, 2, 0]))
|
||||
[[[1, 5], [2, 6]], [[3, 7], [4, 8]]]
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.tensor.array.expressions.array_expressions.PermuteDims
|
||||
|
||||
"""
|
||||
from sympy.tensor.array import SparseNDimArray
|
||||
|
||||
from sympy.tensor.array.expressions.array_expressions import _ArrayExpr
|
||||
from sympy.tensor.array.expressions.array_expressions import _CodegenArrayAbstract
|
||||
from sympy.tensor.array.expressions.array_expressions import _permute_dims
|
||||
from sympy.matrices.expressions.matexpr import MatrixSymbol
|
||||
from sympy.tensor.array.expressions import PermuteDims
|
||||
from sympy.tensor.array.expressions.array_expressions import get_rank
|
||||
perm = PermuteDims._get_permutation_from_arguments(perm, index_order_old, index_order_new, get_rank(expr))
|
||||
if isinstance(expr, (_ArrayExpr, _CodegenArrayAbstract, MatrixSymbol)):
|
||||
return _permute_dims(expr, perm)
|
||||
|
||||
if not isinstance(expr, NDimArray):
|
||||
expr = ImmutableDenseNDimArray(expr)
|
||||
|
||||
from sympy.combinatorics import Permutation
|
||||
if not isinstance(perm, Permutation):
|
||||
perm = Permutation(list(perm))
|
||||
|
||||
if perm.size != expr.rank():
|
||||
raise ValueError("wrong permutation size")
|
||||
|
||||
# Get the inverse permutation:
|
||||
iperm = ~perm
|
||||
new_shape = perm(expr.shape)
|
||||
|
||||
if isinstance(expr, SparseNDimArray):
|
||||
return type(expr)({tuple(perm(expr._get_tuple_index(k))): v
|
||||
for k, v in expr._sparse_array.items()}, new_shape)
|
||||
|
||||
indices_span = perm([range(i) for i in expr.shape])
|
||||
|
||||
new_array = [None]*len(expr)
|
||||
for i, idx in enumerate(itertools.product(*indices_span)):
|
||||
t = iperm(idx)
|
||||
new_array[i] = expr[t]
|
||||
|
||||
return type(expr)(new_array, new_shape)
|
||||
|
||||
|
||||
class Flatten(Printable):
|
||||
"""
|
||||
Flatten an iterable object to a list in a lazy-evaluation way.
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
This class is an iterator with which the memory cost can be economised.
|
||||
Optimisation has been considered to ameliorate the performance for some
|
||||
specific data types like DenseNDimArray and SparseNDimArray.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.tensor.array.arrayop import Flatten
|
||||
>>> from sympy.tensor.array import Array
|
||||
>>> A = Array(range(6)).reshape(2, 3)
|
||||
>>> Flatten(A)
|
||||
Flatten([[0, 1, 2], [3, 4, 5]])
|
||||
>>> [i for i in Flatten(A)]
|
||||
[0, 1, 2, 3, 4, 5]
|
||||
"""
|
||||
def __init__(self, iterable):
|
||||
from sympy.matrices.matrixbase import MatrixBase
|
||||
from sympy.tensor.array import NDimArray
|
||||
|
||||
if not isinstance(iterable, (Iterable, MatrixBase)):
|
||||
raise NotImplementedError("Data type not yet supported")
|
||||
|
||||
if isinstance(iterable, list):
|
||||
iterable = NDimArray(iterable)
|
||||
|
||||
self._iter = iterable
|
||||
self._idx = 0
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def __next__(self):
|
||||
from sympy.matrices.matrixbase import MatrixBase
|
||||
|
||||
if len(self._iter) > self._idx:
|
||||
if isinstance(self._iter, DenseNDimArray):
|
||||
result = self._iter._array[self._idx]
|
||||
|
||||
elif isinstance(self._iter, SparseNDimArray):
|
||||
if self._idx in self._iter._sparse_array:
|
||||
result = self._iter._sparse_array[self._idx]
|
||||
else:
|
||||
result = 0
|
||||
|
||||
elif isinstance(self._iter, MatrixBase):
|
||||
result = self._iter[self._idx]
|
||||
|
||||
elif hasattr(self._iter, '__next__'):
|
||||
result = next(self._iter)
|
||||
|
||||
else:
|
||||
result = self._iter[self._idx]
|
||||
|
||||
else:
|
||||
raise StopIteration
|
||||
|
||||
self._idx += 1
|
||||
return result
|
||||
|
||||
def next(self):
|
||||
return self.__next__()
|
||||
|
||||
def _sympystr(self, printer):
|
||||
return type(self).__name__ + '(' + printer._print(self._iter) + ')'
|
||||
@@ -0,0 +1,206 @@
|
||||
import functools
|
||||
from typing import List
|
||||
|
||||
from sympy.core.basic import Basic
|
||||
from sympy.core.containers import Tuple
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.sympify import _sympify
|
||||
from sympy.tensor.array.mutable_ndim_array import MutableNDimArray
|
||||
from sympy.tensor.array.ndim_array import NDimArray, ImmutableNDimArray, ArrayKind
|
||||
from sympy.utilities.iterables import flatten
|
||||
|
||||
|
||||
class DenseNDimArray(NDimArray):
|
||||
|
||||
_array: List[Basic]
|
||||
|
||||
def __new__(self, *args, **kwargs):
|
||||
return ImmutableDenseNDimArray(*args, **kwargs)
|
||||
|
||||
@property
|
||||
def kind(self) -> ArrayKind:
|
||||
return ArrayKind._union(self._array)
|
||||
|
||||
def __getitem__(self, index):
|
||||
"""
|
||||
Allows to get items from N-dim array.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MutableDenseNDimArray
|
||||
>>> a = MutableDenseNDimArray([0, 1, 2, 3], (2, 2))
|
||||
>>> a
|
||||
[[0, 1], [2, 3]]
|
||||
>>> a[0, 0]
|
||||
0
|
||||
>>> a[1, 1]
|
||||
3
|
||||
>>> a[0]
|
||||
[0, 1]
|
||||
>>> a[1]
|
||||
[2, 3]
|
||||
|
||||
|
||||
Symbolic index:
|
||||
|
||||
>>> from sympy.abc import i, j
|
||||
>>> a[i, j]
|
||||
[[0, 1], [2, 3]][i, j]
|
||||
|
||||
Replace `i` and `j` to get element `(1, 1)`:
|
||||
|
||||
>>> a[i, j].subs({i: 1, j: 1})
|
||||
3
|
||||
|
||||
"""
|
||||
syindex = self._check_symbolic_index(index)
|
||||
if syindex is not None:
|
||||
return syindex
|
||||
|
||||
index = self._check_index_for_getitem(index)
|
||||
|
||||
if isinstance(index, tuple) and any(isinstance(i, slice) for i in index):
|
||||
sl_factors, eindices = self._get_slice_data_for_array_access(index)
|
||||
array = [self._array[self._parse_index(i)] for i in eindices]
|
||||
nshape = [len(el) for i, el in enumerate(sl_factors) if isinstance(index[i], slice)]
|
||||
return type(self)(array, nshape)
|
||||
else:
|
||||
index = self._parse_index(index)
|
||||
return self._array[index]
|
||||
|
||||
@classmethod
|
||||
def zeros(cls, *shape):
|
||||
list_length = functools.reduce(lambda x, y: x*y, shape, S.One)
|
||||
return cls._new(([0]*list_length,), shape)
|
||||
|
||||
def tomatrix(self):
|
||||
"""
|
||||
Converts MutableDenseNDimArray to Matrix. Can convert only 2-dim array, else will raise error.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MutableDenseNDimArray
|
||||
>>> a = MutableDenseNDimArray([1 for i in range(9)], (3, 3))
|
||||
>>> b = a.tomatrix()
|
||||
>>> b
|
||||
Matrix([
|
||||
[1, 1, 1],
|
||||
[1, 1, 1],
|
||||
[1, 1, 1]])
|
||||
|
||||
"""
|
||||
from sympy.matrices import Matrix
|
||||
|
||||
if self.rank() != 2:
|
||||
raise ValueError('Dimensions must be of size of 2')
|
||||
|
||||
return Matrix(self.shape[0], self.shape[1], self._array)
|
||||
|
||||
def reshape(self, *newshape):
|
||||
"""
|
||||
Returns MutableDenseNDimArray instance with new shape. Elements number
|
||||
must be suitable to new shape. The only argument of method sets
|
||||
new shape.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MutableDenseNDimArray
|
||||
>>> a = MutableDenseNDimArray([1, 2, 3, 4, 5, 6], (2, 3))
|
||||
>>> a.shape
|
||||
(2, 3)
|
||||
>>> a
|
||||
[[1, 2, 3], [4, 5, 6]]
|
||||
>>> b = a.reshape(3, 2)
|
||||
>>> b.shape
|
||||
(3, 2)
|
||||
>>> b
|
||||
[[1, 2], [3, 4], [5, 6]]
|
||||
|
||||
"""
|
||||
new_total_size = functools.reduce(lambda x,y: x*y, newshape)
|
||||
if new_total_size != self._loop_size:
|
||||
raise ValueError('Expecting reshape size to %d but got prod(%s) = %d' % (
|
||||
self._loop_size, str(newshape), new_total_size))
|
||||
|
||||
# there is no `.func` as this class does not subtype `Basic`:
|
||||
return type(self)(self._array, newshape)
|
||||
|
||||
|
||||
class ImmutableDenseNDimArray(DenseNDimArray, ImmutableNDimArray): # type: ignore
|
||||
def __new__(cls, iterable, shape=None, **kwargs):
|
||||
return cls._new(iterable, shape, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def _new(cls, iterable, shape, **kwargs):
|
||||
shape, flat_list = cls._handle_ndarray_creation_inputs(iterable, shape, **kwargs)
|
||||
shape = Tuple(*map(_sympify, shape))
|
||||
cls._check_special_bounds(flat_list, shape)
|
||||
flat_list = flatten(flat_list)
|
||||
flat_list = Tuple(*flat_list)
|
||||
self = Basic.__new__(cls, flat_list, shape, **kwargs)
|
||||
self._shape = shape
|
||||
self._array = list(flat_list)
|
||||
self._rank = len(shape)
|
||||
self._loop_size = functools.reduce(lambda x,y: x*y, shape, 1)
|
||||
return self
|
||||
|
||||
def __setitem__(self, index, value):
|
||||
raise TypeError('immutable N-dim array')
|
||||
|
||||
def as_mutable(self):
|
||||
return MutableDenseNDimArray(self)
|
||||
|
||||
def _eval_simplify(self, **kwargs):
|
||||
from sympy.simplify.simplify import simplify
|
||||
return self.applyfunc(simplify)
|
||||
|
||||
class MutableDenseNDimArray(DenseNDimArray, MutableNDimArray):
|
||||
|
||||
def __new__(cls, iterable=None, shape=None, **kwargs):
|
||||
return cls._new(iterable, shape, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def _new(cls, iterable, shape, **kwargs):
|
||||
shape, flat_list = cls._handle_ndarray_creation_inputs(iterable, shape, **kwargs)
|
||||
flat_list = flatten(flat_list)
|
||||
self = object.__new__(cls)
|
||||
self._shape = shape
|
||||
self._array = list(flat_list)
|
||||
self._rank = len(shape)
|
||||
self._loop_size = functools.reduce(lambda x,y: x*y, shape) if shape else len(flat_list)
|
||||
return self
|
||||
|
||||
def __setitem__(self, index, value):
|
||||
"""Allows to set items to MutableDenseNDimArray.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MutableDenseNDimArray
|
||||
>>> a = MutableDenseNDimArray.zeros(2, 2)
|
||||
>>> a[0,0] = 1
|
||||
>>> a[1,1] = 1
|
||||
>>> a
|
||||
[[1, 0], [0, 1]]
|
||||
|
||||
"""
|
||||
if isinstance(index, tuple) and any(isinstance(i, slice) for i in index):
|
||||
value, eindices, slice_offsets = self._get_slice_data_for_array_assignment(index, value)
|
||||
for i in eindices:
|
||||
other_i = [ind - j for ind, j in zip(i, slice_offsets) if j is not None]
|
||||
self._array[self._parse_index(i)] = value[other_i]
|
||||
else:
|
||||
index = self._parse_index(index)
|
||||
self._setter_iterable_check(value)
|
||||
value = _sympify(value)
|
||||
self._array[index] = value
|
||||
|
||||
def as_immutable(self):
|
||||
return ImmutableDenseNDimArray(self)
|
||||
|
||||
@property
|
||||
def free_symbols(self):
|
||||
return {i for j in self._array for i in j.free_symbols}
|
||||
@@ -0,0 +1,178 @@
|
||||
r"""
|
||||
Array expressions are expressions representing N-dimensional arrays, without
|
||||
evaluating them. These expressions represent in a certain way abstract syntax
|
||||
trees of operations on N-dimensional arrays.
|
||||
|
||||
Every N-dimensional array operator has a corresponding array expression object.
|
||||
|
||||
Table of correspondences:
|
||||
|
||||
=============================== =============================
|
||||
Array operator Array expression operator
|
||||
=============================== =============================
|
||||
tensorproduct ArrayTensorProduct
|
||||
tensorcontraction ArrayContraction
|
||||
tensordiagonal ArrayDiagonal
|
||||
permutedims PermuteDims
|
||||
=============================== =============================
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
``ArraySymbol`` objects are the N-dimensional equivalent of ``MatrixSymbol``
|
||||
objects in the matrix module:
|
||||
|
||||
>>> from sympy.tensor.array.expressions import ArraySymbol
|
||||
>>> from sympy.abc import i, j, k
|
||||
>>> A = ArraySymbol("A", (3, 2, 4))
|
||||
>>> A.shape
|
||||
(3, 2, 4)
|
||||
>>> A[i, j, k]
|
||||
A[i, j, k]
|
||||
>>> A.as_explicit()
|
||||
[[[A[0, 0, 0], A[0, 0, 1], A[0, 0, 2], A[0, 0, 3]],
|
||||
[A[0, 1, 0], A[0, 1, 1], A[0, 1, 2], A[0, 1, 3]]],
|
||||
[[A[1, 0, 0], A[1, 0, 1], A[1, 0, 2], A[1, 0, 3]],
|
||||
[A[1, 1, 0], A[1, 1, 1], A[1, 1, 2], A[1, 1, 3]]],
|
||||
[[A[2, 0, 0], A[2, 0, 1], A[2, 0, 2], A[2, 0, 3]],
|
||||
[A[2, 1, 0], A[2, 1, 1], A[2, 1, 2], A[2, 1, 3]]]]
|
||||
|
||||
Component-explicit arrays can be added inside array expressions:
|
||||
|
||||
>>> from sympy import Array
|
||||
>>> from sympy import tensorproduct
|
||||
>>> from sympy.tensor.array.expressions import ArrayTensorProduct
|
||||
>>> a = Array([1, 2, 3])
|
||||
>>> b = Array([i, j, k])
|
||||
>>> expr = ArrayTensorProduct(a, b, b)
|
||||
>>> expr
|
||||
ArrayTensorProduct([1, 2, 3], [i, j, k], [i, j, k])
|
||||
>>> expr.as_explicit() == tensorproduct(a, b, b)
|
||||
True
|
||||
|
||||
Constructing array expressions from index-explicit forms
|
||||
--------------------------------------------------------
|
||||
|
||||
Array expressions are index-implicit. This means they do not use any indices to
|
||||
represent array operations. The function ``convert_indexed_to_array( ... )``
|
||||
may be used to convert index-explicit expressions to array expressions.
|
||||
It takes as input two parameters: the index-explicit expression and the order
|
||||
of the indices:
|
||||
|
||||
>>> from sympy.tensor.array.expressions import convert_indexed_to_array
|
||||
>>> from sympy import Sum
|
||||
>>> A = ArraySymbol("A", (3, 3))
|
||||
>>> B = ArraySymbol("B", (3, 3))
|
||||
>>> convert_indexed_to_array(A[i, j], [i, j])
|
||||
A
|
||||
>>> convert_indexed_to_array(A[i, j], [j, i])
|
||||
PermuteDims(A, (0 1))
|
||||
>>> convert_indexed_to_array(A[i, j] + B[j, i], [i, j])
|
||||
ArrayAdd(A, PermuteDims(B, (0 1)))
|
||||
>>> convert_indexed_to_array(Sum(A[i, j]*B[j, k], (j, 0, 2)), [i, k])
|
||||
ArrayContraction(ArrayTensorProduct(A, B), (1, 2))
|
||||
|
||||
The diagonal of a matrix in the array expression form:
|
||||
|
||||
>>> convert_indexed_to_array(A[i, i], [i])
|
||||
ArrayDiagonal(A, (0, 1))
|
||||
|
||||
The trace of a matrix in the array expression form:
|
||||
|
||||
>>> convert_indexed_to_array(Sum(A[i, i], (i, 0, 2)), [i])
|
||||
ArrayContraction(A, (0, 1))
|
||||
|
||||
Compatibility with matrices
|
||||
---------------------------
|
||||
|
||||
Array expressions can be mixed with objects from the matrix module:
|
||||
|
||||
>>> from sympy import MatrixSymbol
|
||||
>>> from sympy.tensor.array.expressions import ArrayContraction
|
||||
>>> M = MatrixSymbol("M", 3, 3)
|
||||
>>> N = MatrixSymbol("N", 3, 3)
|
||||
|
||||
Express the matrix product in the array expression form:
|
||||
|
||||
>>> from sympy.tensor.array.expressions import convert_matrix_to_array
|
||||
>>> expr = convert_matrix_to_array(M*N)
|
||||
>>> expr
|
||||
ArrayContraction(ArrayTensorProduct(M, N), (1, 2))
|
||||
|
||||
The expression can be converted back to matrix form:
|
||||
|
||||
>>> from sympy.tensor.array.expressions import convert_array_to_matrix
|
||||
>>> convert_array_to_matrix(expr)
|
||||
M*N
|
||||
|
||||
Add a second contraction on the remaining axes in order to get the trace of `M \cdot N`:
|
||||
|
||||
>>> expr_tr = ArrayContraction(expr, (0, 1))
|
||||
>>> expr_tr
|
||||
ArrayContraction(ArrayContraction(ArrayTensorProduct(M, N), (1, 2)), (0, 1))
|
||||
|
||||
Flatten the expression by calling ``.doit()`` and remove the nested array contraction operations:
|
||||
|
||||
>>> expr_tr.doit()
|
||||
ArrayContraction(ArrayTensorProduct(M, N), (0, 3), (1, 2))
|
||||
|
||||
Get the explicit form of the array expression:
|
||||
|
||||
>>> expr.as_explicit()
|
||||
[[M[0, 0]*N[0, 0] + M[0, 1]*N[1, 0] + M[0, 2]*N[2, 0], M[0, 0]*N[0, 1] + M[0, 1]*N[1, 1] + M[0, 2]*N[2, 1], M[0, 0]*N[0, 2] + M[0, 1]*N[1, 2] + M[0, 2]*N[2, 2]],
|
||||
[M[1, 0]*N[0, 0] + M[1, 1]*N[1, 0] + M[1, 2]*N[2, 0], M[1, 0]*N[0, 1] + M[1, 1]*N[1, 1] + M[1, 2]*N[2, 1], M[1, 0]*N[0, 2] + M[1, 1]*N[1, 2] + M[1, 2]*N[2, 2]],
|
||||
[M[2, 0]*N[0, 0] + M[2, 1]*N[1, 0] + M[2, 2]*N[2, 0], M[2, 0]*N[0, 1] + M[2, 1]*N[1, 1] + M[2, 2]*N[2, 1], M[2, 0]*N[0, 2] + M[2, 1]*N[1, 2] + M[2, 2]*N[2, 2]]]
|
||||
|
||||
Express the trace of a matrix:
|
||||
|
||||
>>> from sympy import Trace
|
||||
>>> convert_matrix_to_array(Trace(M))
|
||||
ArrayContraction(M, (0, 1))
|
||||
>>> convert_matrix_to_array(Trace(M*N))
|
||||
ArrayContraction(ArrayTensorProduct(M, N), (0, 3), (1, 2))
|
||||
|
||||
Express the transposition of a matrix (will be expressed as a permutation of the axes:
|
||||
|
||||
>>> convert_matrix_to_array(M.T)
|
||||
PermuteDims(M, (0 1))
|
||||
|
||||
Compute the derivative array expressions:
|
||||
|
||||
>>> from sympy.tensor.array.expressions import array_derive
|
||||
>>> d = array_derive(M, M)
|
||||
>>> d
|
||||
PermuteDims(ArrayTensorProduct(I, I), (3)(1 2))
|
||||
|
||||
Verify that the derivative corresponds to the form computed with explicit matrices:
|
||||
|
||||
>>> d.as_explicit()
|
||||
[[[[1, 0, 0], [0, 0, 0], [0, 0, 0]], [[0, 1, 0], [0, 0, 0], [0, 0, 0]], [[0, 0, 1], [0, 0, 0], [0, 0, 0]]], [[[0, 0, 0], [1, 0, 0], [0, 0, 0]], [[0, 0, 0], [0, 1, 0], [0, 0, 0]], [[0, 0, 0], [0, 0, 1], [0, 0, 0]]], [[[0, 0, 0], [0, 0, 0], [1, 0, 0]], [[0, 0, 0], [0, 0, 0], [0, 1, 0]], [[0, 0, 0], [0, 0, 0], [0, 0, 1]]]]
|
||||
>>> Me = M.as_explicit()
|
||||
>>> Me.diff(Me)
|
||||
[[[[1, 0, 0], [0, 0, 0], [0, 0, 0]], [[0, 1, 0], [0, 0, 0], [0, 0, 0]], [[0, 0, 1], [0, 0, 0], [0, 0, 0]]], [[[0, 0, 0], [1, 0, 0], [0, 0, 0]], [[0, 0, 0], [0, 1, 0], [0, 0, 0]], [[0, 0, 0], [0, 0, 1], [0, 0, 0]]], [[[0, 0, 0], [0, 0, 0], [1, 0, 0]], [[0, 0, 0], [0, 0, 0], [0, 1, 0]], [[0, 0, 0], [0, 0, 0], [0, 0, 1]]]]
|
||||
|
||||
"""
|
||||
|
||||
__all__ = [
|
||||
"ArraySymbol", "ArrayElement", "ZeroArray", "OneArray",
|
||||
"ArrayTensorProduct",
|
||||
"ArrayContraction",
|
||||
"ArrayDiagonal",
|
||||
"PermuteDims",
|
||||
"ArrayAdd",
|
||||
"ArrayElementwiseApplyFunc",
|
||||
"Reshape",
|
||||
"convert_array_to_matrix",
|
||||
"convert_matrix_to_array",
|
||||
"convert_array_to_indexed",
|
||||
"convert_indexed_to_array",
|
||||
"array_derive",
|
||||
]
|
||||
|
||||
from sympy.tensor.array.expressions.array_expressions import ArrayTensorProduct, ArrayAdd, PermuteDims, ArrayDiagonal, \
|
||||
ArrayContraction, Reshape, ArraySymbol, ArrayElement, ZeroArray, OneArray, ArrayElementwiseApplyFunc
|
||||
from sympy.tensor.array.expressions.arrayexpr_derivatives import array_derive
|
||||
from sympy.tensor.array.expressions.from_array_to_indexed import convert_array_to_indexed
|
||||
from sympy.tensor.array.expressions.from_array_to_matrix import convert_array_to_matrix
|
||||
from sympy.tensor.array.expressions.from_indexed_to_array import convert_indexed_to_array
|
||||
from sympy.tensor.array.expressions.from_matrix_to_array import convert_matrix_to_array
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,194 @@
|
||||
import operator
|
||||
from functools import reduce, singledispatch
|
||||
|
||||
from sympy.core.expr import Expr
|
||||
from sympy.core.singleton import S
|
||||
from sympy.matrices.expressions.hadamard import HadamardProduct
|
||||
from sympy.matrices.expressions.inverse import Inverse
|
||||
from sympy.matrices.expressions.matexpr import (MatrixExpr, MatrixSymbol)
|
||||
from sympy.matrices.expressions.special import Identity, OneMatrix
|
||||
from sympy.matrices.expressions.transpose import Transpose
|
||||
from sympy.combinatorics.permutations import _af_invert
|
||||
from sympy.matrices.expressions.applyfunc import ElementwiseApplyFunction
|
||||
from sympy.tensor.array.expressions.array_expressions import (
|
||||
_ArrayExpr, ZeroArray, ArraySymbol, ArrayTensorProduct, ArrayAdd,
|
||||
PermuteDims, ArrayDiagonal, ArrayElementwiseApplyFunc, get_rank,
|
||||
get_shape, ArrayContraction, _array_tensor_product, _array_contraction,
|
||||
_array_diagonal, _array_add, _permute_dims, Reshape)
|
||||
from sympy.tensor.array.expressions.from_matrix_to_array import convert_matrix_to_array
|
||||
|
||||
|
||||
@singledispatch
|
||||
def array_derive(expr, x):
|
||||
"""
|
||||
Derivatives (gradients) for array expressions.
|
||||
"""
|
||||
raise NotImplementedError(f"not implemented for type {type(expr)}")
|
||||
|
||||
|
||||
@array_derive.register(Expr)
|
||||
def _(expr: Expr, x: _ArrayExpr):
|
||||
return ZeroArray(*x.shape)
|
||||
|
||||
|
||||
@array_derive.register(ArrayTensorProduct)
|
||||
def _(expr: ArrayTensorProduct, x: Expr):
|
||||
args = expr.args
|
||||
addend_list = []
|
||||
for i, arg in enumerate(expr.args):
|
||||
darg = array_derive(arg, x)
|
||||
if darg == 0:
|
||||
continue
|
||||
args_prev = args[:i]
|
||||
args_succ = args[i+1:]
|
||||
shape_prev = reduce(operator.add, map(get_shape, args_prev), ())
|
||||
shape_succ = reduce(operator.add, map(get_shape, args_succ), ())
|
||||
addend = _array_tensor_product(*args_prev, darg, *args_succ)
|
||||
tot1 = len(get_shape(x))
|
||||
tot2 = tot1 + len(shape_prev)
|
||||
tot3 = tot2 + len(get_shape(arg))
|
||||
tot4 = tot3 + len(shape_succ)
|
||||
perm = list(range(tot1, tot2)) + \
|
||||
list(range(tot1)) + list(range(tot2, tot3)) + \
|
||||
list(range(tot3, tot4))
|
||||
addend = _permute_dims(addend, _af_invert(perm))
|
||||
addend_list.append(addend)
|
||||
if len(addend_list) == 1:
|
||||
return addend_list[0]
|
||||
elif len(addend_list) == 0:
|
||||
return S.Zero
|
||||
else:
|
||||
return _array_add(*addend_list)
|
||||
|
||||
|
||||
@array_derive.register(ArraySymbol)
|
||||
def _(expr: ArraySymbol, x: _ArrayExpr):
|
||||
if expr == x:
|
||||
return _permute_dims(
|
||||
ArrayTensorProduct.fromiter(Identity(i) for i in expr.shape),
|
||||
[2*i for i in range(len(expr.shape))] + [2*i+1 for i in range(len(expr.shape))]
|
||||
)
|
||||
return ZeroArray(*(x.shape + expr.shape))
|
||||
|
||||
|
||||
@array_derive.register(MatrixSymbol)
|
||||
def _(expr: MatrixSymbol, x: _ArrayExpr):
|
||||
m, n = expr.shape
|
||||
if expr == x:
|
||||
return _permute_dims(
|
||||
_array_tensor_product(Identity(m), Identity(n)),
|
||||
[0, 2, 1, 3]
|
||||
)
|
||||
return ZeroArray(*(x.shape + expr.shape))
|
||||
|
||||
|
||||
@array_derive.register(Identity)
|
||||
def _(expr: Identity, x: _ArrayExpr):
|
||||
return ZeroArray(*(x.shape + expr.shape))
|
||||
|
||||
|
||||
@array_derive.register(OneMatrix)
|
||||
def _(expr: OneMatrix, x: _ArrayExpr):
|
||||
return ZeroArray(*(x.shape + expr.shape))
|
||||
|
||||
|
||||
@array_derive.register(Transpose)
|
||||
def _(expr: Transpose, x: Expr):
|
||||
# D(A.T, A) ==> (m,n,i,j) ==> D(A_ji, A_mn) = d_mj d_ni
|
||||
# D(B.T, A) ==> (m,n,i,j) ==> D(B_ji, A_mn)
|
||||
fd = array_derive(expr.arg, x)
|
||||
return _permute_dims(fd, [0, 1, 3, 2])
|
||||
|
||||
|
||||
@array_derive.register(Inverse)
|
||||
def _(expr: Inverse, x: Expr):
|
||||
mat = expr.I
|
||||
dexpr = array_derive(mat, x)
|
||||
tp = _array_tensor_product(-expr, dexpr, expr)
|
||||
mp = _array_contraction(tp, (1, 4), (5, 6))
|
||||
pp = _permute_dims(mp, [1, 2, 0, 3])
|
||||
return pp
|
||||
|
||||
|
||||
@array_derive.register(ElementwiseApplyFunction)
|
||||
def _(expr: ElementwiseApplyFunction, x: Expr):
|
||||
assert get_rank(expr) == 2
|
||||
assert get_rank(x) == 2
|
||||
fdiff = expr._get_function_fdiff()
|
||||
dexpr = array_derive(expr.expr, x)
|
||||
tp = _array_tensor_product(
|
||||
ElementwiseApplyFunction(fdiff, expr.expr),
|
||||
dexpr
|
||||
)
|
||||
td = _array_diagonal(
|
||||
tp, (0, 4), (1, 5)
|
||||
)
|
||||
return td
|
||||
|
||||
|
||||
@array_derive.register(ArrayElementwiseApplyFunc)
|
||||
def _(expr: ArrayElementwiseApplyFunc, x: Expr):
|
||||
fdiff = expr._get_function_fdiff()
|
||||
subexpr = expr.expr
|
||||
dsubexpr = array_derive(subexpr, x)
|
||||
tp = _array_tensor_product(
|
||||
dsubexpr,
|
||||
ArrayElementwiseApplyFunc(fdiff, subexpr)
|
||||
)
|
||||
b = get_rank(x)
|
||||
c = get_rank(expr)
|
||||
diag_indices = [(b + i, b + c + i) for i in range(c)]
|
||||
return _array_diagonal(tp, *diag_indices)
|
||||
|
||||
|
||||
@array_derive.register(MatrixExpr)
|
||||
def _(expr: MatrixExpr, x: Expr):
|
||||
cg = convert_matrix_to_array(expr)
|
||||
return array_derive(cg, x)
|
||||
|
||||
|
||||
@array_derive.register(HadamardProduct)
|
||||
def _(expr: HadamardProduct, x: Expr):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
@array_derive.register(ArrayContraction)
|
||||
def _(expr: ArrayContraction, x: Expr):
|
||||
fd = array_derive(expr.expr, x)
|
||||
rank_x = len(get_shape(x))
|
||||
contraction_indices = expr.contraction_indices
|
||||
new_contraction_indices = [tuple(j + rank_x for j in i) for i in contraction_indices]
|
||||
return _array_contraction(fd, *new_contraction_indices)
|
||||
|
||||
|
||||
@array_derive.register(ArrayDiagonal)
|
||||
def _(expr: ArrayDiagonal, x: Expr):
|
||||
dsubexpr = array_derive(expr.expr, x)
|
||||
rank_x = len(get_shape(x))
|
||||
diag_indices = [[j + rank_x for j in i] for i in expr.diagonal_indices]
|
||||
return _array_diagonal(dsubexpr, *diag_indices)
|
||||
|
||||
|
||||
@array_derive.register(ArrayAdd)
|
||||
def _(expr: ArrayAdd, x: Expr):
|
||||
return _array_add(*[array_derive(arg, x) for arg in expr.args])
|
||||
|
||||
|
||||
@array_derive.register(PermuteDims)
|
||||
def _(expr: PermuteDims, x: Expr):
|
||||
de = array_derive(expr.expr, x)
|
||||
perm = [0, 1] + [i + 2 for i in expr.permutation.array_form]
|
||||
return _permute_dims(de, perm)
|
||||
|
||||
|
||||
@array_derive.register(Reshape)
|
||||
def _(expr: Reshape, x: Expr):
|
||||
de = array_derive(expr.expr, x)
|
||||
return Reshape(de, get_shape(x) + expr.shape)
|
||||
|
||||
|
||||
def matrix_derive(expr, x):
|
||||
from sympy.tensor.array.expressions.from_array_to_matrix import convert_array_to_matrix
|
||||
ce = convert_matrix_to_array(expr)
|
||||
dce = array_derive(ce, x)
|
||||
return convert_array_to_matrix(dce).doit()
|
||||
@@ -0,0 +1,12 @@
|
||||
from sympy.tensor.array.expressions import from_array_to_indexed
|
||||
from sympy.utilities.decorator import deprecated
|
||||
|
||||
|
||||
_conv_to_from_decorator = deprecated(
|
||||
"module has been renamed by replacing 'conv_' with 'from_' in its name",
|
||||
deprecated_since_version="1.11",
|
||||
active_deprecations_target="deprecated-conv-array-expr-module-names",
|
||||
)
|
||||
|
||||
|
||||
convert_array_to_indexed = _conv_to_from_decorator(from_array_to_indexed.convert_array_to_indexed)
|
||||
@@ -0,0 +1,6 @@
|
||||
from sympy.tensor.array.expressions import from_array_to_matrix
|
||||
from sympy.tensor.array.expressions.conv_array_to_indexed import _conv_to_from_decorator
|
||||
|
||||
convert_array_to_matrix = _conv_to_from_decorator(from_array_to_matrix.convert_array_to_matrix)
|
||||
_array2matrix = _conv_to_from_decorator(from_array_to_matrix._array2matrix)
|
||||
_remove_trivial_dims = _conv_to_from_decorator(from_array_to_matrix._remove_trivial_dims)
|
||||
@@ -0,0 +1,4 @@
|
||||
from sympy.tensor.array.expressions import from_indexed_to_array
|
||||
from sympy.tensor.array.expressions.conv_array_to_indexed import _conv_to_from_decorator
|
||||
|
||||
convert_indexed_to_array = _conv_to_from_decorator(from_indexed_to_array.convert_indexed_to_array)
|
||||
@@ -0,0 +1,4 @@
|
||||
from sympy.tensor.array.expressions import from_matrix_to_array
|
||||
from sympy.tensor.array.expressions.conv_array_to_indexed import _conv_to_from_decorator
|
||||
|
||||
convert_matrix_to_array = _conv_to_from_decorator(from_matrix_to_array.convert_matrix_to_array)
|
||||
@@ -0,0 +1,84 @@
|
||||
import collections.abc
|
||||
import operator
|
||||
from itertools import accumulate
|
||||
|
||||
from sympy import Mul, Sum, Dummy, Add
|
||||
from sympy.tensor.array.expressions import PermuteDims, ArrayAdd, ArrayElementwiseApplyFunc, Reshape
|
||||
from sympy.tensor.array.expressions.array_expressions import ArrayTensorProduct, get_rank, ArrayContraction, \
|
||||
ArrayDiagonal, get_shape, _get_array_element_or_slice, _ArrayExpr
|
||||
from sympy.tensor.array.expressions.utils import _apply_permutation_to_list
|
||||
|
||||
|
||||
def convert_array_to_indexed(expr, indices):
|
||||
return _ConvertArrayToIndexed().do_convert(expr, indices)
|
||||
|
||||
|
||||
class _ConvertArrayToIndexed:
|
||||
|
||||
def __init__(self):
|
||||
self.count_dummies = 0
|
||||
|
||||
def do_convert(self, expr, indices):
|
||||
if isinstance(expr, ArrayTensorProduct):
|
||||
cumul = list(accumulate([0] + [get_rank(arg) for arg in expr.args]))
|
||||
indices_grp = [indices[cumul[i]:cumul[i+1]] for i in range(len(expr.args))]
|
||||
return Mul.fromiter(self.do_convert(arg, ind) for arg, ind in zip(expr.args, indices_grp))
|
||||
if isinstance(expr, ArrayContraction):
|
||||
new_indices = [None for i in range(get_rank(expr.expr))]
|
||||
limits = []
|
||||
bottom_shape = get_shape(expr.expr)
|
||||
for contraction_index_grp in expr.contraction_indices:
|
||||
d = Dummy(f"d{self.count_dummies}")
|
||||
self.count_dummies += 1
|
||||
dim = bottom_shape[contraction_index_grp[0]]
|
||||
limits.append((d, 0, dim-1))
|
||||
for i in contraction_index_grp:
|
||||
new_indices[i] = d
|
||||
j = 0
|
||||
for i in range(len(new_indices)):
|
||||
if new_indices[i] is None:
|
||||
new_indices[i] = indices[j]
|
||||
j += 1
|
||||
newexpr = self.do_convert(expr.expr, new_indices)
|
||||
return Sum(newexpr, *limits)
|
||||
if isinstance(expr, ArrayDiagonal):
|
||||
new_indices = [None for i in range(get_rank(expr.expr))]
|
||||
ind_pos = expr._push_indices_down(expr.diagonal_indices, list(range(len(indices))), get_rank(expr))
|
||||
for i, index in zip(ind_pos, indices):
|
||||
if isinstance(i, collections.abc.Iterable):
|
||||
for j in i:
|
||||
new_indices[j] = index
|
||||
else:
|
||||
new_indices[i] = index
|
||||
newexpr = self.do_convert(expr.expr, new_indices)
|
||||
return newexpr
|
||||
if isinstance(expr, PermuteDims):
|
||||
permuted_indices = _apply_permutation_to_list(expr.permutation, indices)
|
||||
return self.do_convert(expr.expr, permuted_indices)
|
||||
if isinstance(expr, ArrayAdd):
|
||||
return Add.fromiter(self.do_convert(arg, indices) for arg in expr.args)
|
||||
if isinstance(expr, _ArrayExpr):
|
||||
return expr.__getitem__(tuple(indices))
|
||||
if isinstance(expr, ArrayElementwiseApplyFunc):
|
||||
return expr.function(self.do_convert(expr.expr, indices))
|
||||
if isinstance(expr, Reshape):
|
||||
shape_up = expr.shape
|
||||
shape_down = get_shape(expr.expr)
|
||||
cumul = list(accumulate([1] + list(reversed(shape_up)), operator.mul))
|
||||
one_index = Add.fromiter(i*s for i, s in zip(reversed(indices), cumul))
|
||||
dest_indices = [None for _ in shape_down]
|
||||
c = 1
|
||||
for i, e in enumerate(reversed(shape_down)):
|
||||
if c == 1:
|
||||
if i == len(shape_down) - 1:
|
||||
dest_indices[i] = one_index
|
||||
else:
|
||||
dest_indices[i] = one_index % e
|
||||
elif i == len(shape_down) - 1:
|
||||
dest_indices[i] = one_index // c
|
||||
else:
|
||||
dest_indices[i] = one_index // c % e
|
||||
c *= e
|
||||
dest_indices.reverse()
|
||||
return self.do_convert(expr.expr, dest_indices)
|
||||
return _get_array_element_or_slice(expr, indices)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,257 @@
|
||||
from collections import defaultdict
|
||||
|
||||
from sympy import Function
|
||||
from sympy.combinatorics.permutations import _af_invert
|
||||
from sympy.concrete.summations import Sum
|
||||
from sympy.core.add import Add
|
||||
from sympy.core.mul import Mul
|
||||
from sympy.core.numbers import Integer
|
||||
from sympy.core.power import Pow
|
||||
from sympy.core.sorting import default_sort_key
|
||||
from sympy.functions.special.tensor_functions import KroneckerDelta
|
||||
from sympy.tensor.array.expressions import ArrayElementwiseApplyFunc
|
||||
from sympy.tensor.indexed import (Indexed, IndexedBase)
|
||||
from sympy.combinatorics import Permutation
|
||||
from sympy.matrices.expressions.matexpr import MatrixElement
|
||||
from sympy.tensor.array.expressions.array_expressions import ArrayDiagonal, \
|
||||
get_shape, ArrayElement, _array_tensor_product, _array_diagonal, _array_contraction, _array_add, \
|
||||
_permute_dims, OneArray, ArrayAdd
|
||||
from sympy.tensor.array.expressions.utils import _get_argindex, _get_diagonal_indices
|
||||
|
||||
|
||||
def convert_indexed_to_array(expr, first_indices=None):
|
||||
r"""
|
||||
Parse indexed expression into a form useful for code generation.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.tensor.array.expressions.from_indexed_to_array import convert_indexed_to_array
|
||||
>>> from sympy import MatrixSymbol, Sum, symbols
|
||||
|
||||
>>> i, j, k, d = symbols("i j k d")
|
||||
>>> M = MatrixSymbol("M", d, d)
|
||||
>>> N = MatrixSymbol("N", d, d)
|
||||
|
||||
Recognize the trace in summation form:
|
||||
|
||||
>>> expr = Sum(M[i, i], (i, 0, d-1))
|
||||
>>> convert_indexed_to_array(expr)
|
||||
ArrayContraction(M, (0, 1))
|
||||
|
||||
Recognize the extraction of the diagonal by using the same index `i` on
|
||||
both axes of the matrix:
|
||||
|
||||
>>> expr = M[i, i]
|
||||
>>> convert_indexed_to_array(expr)
|
||||
ArrayDiagonal(M, (0, 1))
|
||||
|
||||
This function can help perform the transformation expressed in two
|
||||
different mathematical notations as:
|
||||
|
||||
`\sum_{j=0}^{N-1} A_{i,j} B_{j,k} \Longrightarrow \mathbf{A}\cdot \mathbf{B}`
|
||||
|
||||
Recognize the matrix multiplication in summation form:
|
||||
|
||||
>>> expr = Sum(M[i, j]*N[j, k], (j, 0, d-1))
|
||||
>>> convert_indexed_to_array(expr)
|
||||
ArrayContraction(ArrayTensorProduct(M, N), (1, 2))
|
||||
|
||||
Specify that ``k`` has to be the starting index:
|
||||
|
||||
>>> convert_indexed_to_array(expr, first_indices=[k])
|
||||
ArrayContraction(ArrayTensorProduct(N, M), (0, 3))
|
||||
"""
|
||||
|
||||
result, indices = _convert_indexed_to_array(expr)
|
||||
|
||||
if any(isinstance(i, (int, Integer)) for i in indices):
|
||||
result = ArrayElement(result, indices)
|
||||
indices = []
|
||||
|
||||
if not first_indices:
|
||||
return result
|
||||
|
||||
def _check_is_in(elem, indices):
|
||||
if elem in indices:
|
||||
return True
|
||||
if any(elem in i for i in indices if isinstance(i, frozenset)):
|
||||
return True
|
||||
return False
|
||||
|
||||
repl = {j: i for i in indices if isinstance(i, frozenset) for j in i}
|
||||
first_indices = [repl.get(i, i) for i in first_indices]
|
||||
for i in first_indices:
|
||||
if not _check_is_in(i, indices):
|
||||
first_indices.remove(i)
|
||||
first_indices.extend([i for i in indices if not _check_is_in(i, first_indices)])
|
||||
|
||||
def _get_pos(elem, indices):
|
||||
if elem in indices:
|
||||
return indices.index(elem)
|
||||
for i, e in enumerate(indices):
|
||||
if not isinstance(e, frozenset):
|
||||
continue
|
||||
if elem in e:
|
||||
return i
|
||||
raise ValueError("not found")
|
||||
|
||||
permutation = _af_invert([_get_pos(i, first_indices) for i in indices])
|
||||
if isinstance(result, ArrayAdd):
|
||||
return _array_add(*[_permute_dims(arg, permutation) for arg in result.args])
|
||||
else:
|
||||
return _permute_dims(result, permutation)
|
||||
|
||||
|
||||
def _convert_indexed_to_array(expr):
|
||||
if isinstance(expr, Sum):
|
||||
function = expr.function
|
||||
summation_indices = expr.variables
|
||||
subexpr, subindices = _convert_indexed_to_array(function)
|
||||
subindicessets = {j: i for i in subindices if isinstance(i, frozenset) for j in i}
|
||||
summation_indices = sorted({subindicessets.get(i, i) for i in summation_indices}, key=default_sort_key)
|
||||
# TODO: check that Kronecker delta is only contracted to one other element:
|
||||
kronecker_indices = set()
|
||||
if isinstance(function, Mul):
|
||||
for arg in function.args:
|
||||
if not isinstance(arg, KroneckerDelta):
|
||||
continue
|
||||
arg_indices = sorted(set(arg.indices), key=default_sort_key)
|
||||
if len(arg_indices) == 2:
|
||||
kronecker_indices.update(arg_indices)
|
||||
kronecker_indices = sorted(kronecker_indices, key=default_sort_key)
|
||||
# Check dimensional consistency:
|
||||
shape = get_shape(subexpr)
|
||||
if shape:
|
||||
for ind, istart, iend in expr.limits:
|
||||
i = _get_argindex(subindices, ind)
|
||||
if istart != 0 or iend+1 != shape[i]:
|
||||
raise ValueError("summation index and array dimension mismatch: %s" % ind)
|
||||
contraction_indices = []
|
||||
subindices = list(subindices)
|
||||
if isinstance(subexpr, ArrayDiagonal):
|
||||
diagonal_indices = list(subexpr.diagonal_indices)
|
||||
dindices = subindices[-len(diagonal_indices):]
|
||||
subindices = subindices[:-len(diagonal_indices)]
|
||||
for index in summation_indices:
|
||||
if index in dindices:
|
||||
position = dindices.index(index)
|
||||
contraction_indices.append(diagonal_indices[position])
|
||||
diagonal_indices[position] = None
|
||||
diagonal_indices = [i for i in diagonal_indices if i is not None]
|
||||
for i, ind in enumerate(subindices):
|
||||
if ind in summation_indices:
|
||||
pass
|
||||
if diagonal_indices:
|
||||
subexpr = _array_diagonal(subexpr.expr, *diagonal_indices)
|
||||
else:
|
||||
subexpr = subexpr.expr
|
||||
|
||||
axes_contraction = defaultdict(list)
|
||||
for i, ind in enumerate(subindices):
|
||||
include = all(j not in kronecker_indices for j in ind) if isinstance(ind, frozenset) else ind not in kronecker_indices
|
||||
if ind in summation_indices and include:
|
||||
axes_contraction[ind].append(i)
|
||||
subindices[i] = None
|
||||
for k, v in axes_contraction.items():
|
||||
if any(i in kronecker_indices for i in k) if isinstance(k, frozenset) else k in kronecker_indices:
|
||||
continue
|
||||
contraction_indices.append(tuple(v))
|
||||
free_indices = [i for i in subindices if i is not None]
|
||||
indices_ret = list(free_indices)
|
||||
indices_ret.sort(key=lambda x: free_indices.index(x))
|
||||
return _array_contraction(
|
||||
subexpr,
|
||||
*contraction_indices,
|
||||
free_indices=free_indices
|
||||
), tuple(indices_ret)
|
||||
if isinstance(expr, Mul):
|
||||
args, indices = zip(*[_convert_indexed_to_array(arg) for arg in expr.args])
|
||||
# Check if there are KroneckerDelta objects:
|
||||
kronecker_delta_repl = {}
|
||||
for arg in args:
|
||||
if not isinstance(arg, KroneckerDelta):
|
||||
continue
|
||||
# Diagonalize two indices:
|
||||
i, j = arg.indices
|
||||
kindices = set(arg.indices)
|
||||
if i in kronecker_delta_repl:
|
||||
kindices.update(kronecker_delta_repl[i])
|
||||
if j in kronecker_delta_repl:
|
||||
kindices.update(kronecker_delta_repl[j])
|
||||
kindices = frozenset(kindices)
|
||||
for index in kindices:
|
||||
kronecker_delta_repl[index] = kindices
|
||||
# Remove KroneckerDelta objects, their relations should be handled by
|
||||
# ArrayDiagonal:
|
||||
newargs = []
|
||||
newindices = []
|
||||
for arg, loc_indices in zip(args, indices):
|
||||
if isinstance(arg, KroneckerDelta):
|
||||
continue
|
||||
newargs.append(arg)
|
||||
newindices.append(loc_indices)
|
||||
flattened_indices = [kronecker_delta_repl.get(j, j) for i in newindices for j in i]
|
||||
diagonal_indices, ret_indices = _get_diagonal_indices(flattened_indices)
|
||||
tp = _array_tensor_product(*newargs)
|
||||
if diagonal_indices:
|
||||
return _array_diagonal(tp, *diagonal_indices), ret_indices
|
||||
else:
|
||||
return tp, ret_indices
|
||||
if isinstance(expr, MatrixElement):
|
||||
indices = expr.args[1:]
|
||||
diagonal_indices, ret_indices = _get_diagonal_indices(indices)
|
||||
if diagonal_indices:
|
||||
return _array_diagonal(expr.args[0], *diagonal_indices), ret_indices
|
||||
else:
|
||||
return expr.args[0], ret_indices
|
||||
if isinstance(expr, ArrayElement):
|
||||
indices = expr.indices
|
||||
diagonal_indices, ret_indices = _get_diagonal_indices(indices)
|
||||
if diagonal_indices:
|
||||
return _array_diagonal(expr.name, *diagonal_indices), ret_indices
|
||||
else:
|
||||
return expr.name, ret_indices
|
||||
if isinstance(expr, Indexed):
|
||||
indices = expr.indices
|
||||
diagonal_indices, ret_indices = _get_diagonal_indices(indices)
|
||||
if diagonal_indices:
|
||||
return _array_diagonal(expr.base, *diagonal_indices), ret_indices
|
||||
else:
|
||||
return expr.args[0], ret_indices
|
||||
if isinstance(expr, IndexedBase):
|
||||
raise NotImplementedError
|
||||
if isinstance(expr, KroneckerDelta):
|
||||
return expr, expr.indices
|
||||
if isinstance(expr, Add):
|
||||
args, indices = zip(*[_convert_indexed_to_array(arg) for arg in expr.args])
|
||||
args = list(args)
|
||||
# Check if all indices are compatible. Otherwise expand the dimensions:
|
||||
index0 = []
|
||||
shape0 = []
|
||||
for arg, arg_indices in zip(args, indices):
|
||||
arg_indices_set = set(arg_indices)
|
||||
arg_indices_missing = arg_indices_set.difference(index0)
|
||||
index0.extend([i for i in arg_indices if i in arg_indices_missing])
|
||||
arg_shape = get_shape(arg)
|
||||
shape0.extend([arg_shape[i] for i, e in enumerate(arg_indices) if e in arg_indices_missing])
|
||||
for i, (arg, arg_indices) in enumerate(zip(args, indices)):
|
||||
if len(arg_indices) < len(index0):
|
||||
missing_indices_pos = [i for i, e in enumerate(index0) if e not in arg_indices]
|
||||
missing_shape = [shape0[i] for i in missing_indices_pos]
|
||||
arg_indices = tuple(index0[j] for j in missing_indices_pos) + arg_indices
|
||||
args[i] = _array_tensor_product(OneArray(*missing_shape), args[i])
|
||||
permutation = Permutation([arg_indices.index(j) for j in index0])
|
||||
# Perform index permutations:
|
||||
args[i] = _permute_dims(args[i], permutation)
|
||||
return _array_add(*args), tuple(index0)
|
||||
if isinstance(expr, Pow):
|
||||
subexpr, subindices = _convert_indexed_to_array(expr.base)
|
||||
if isinstance(expr.exp, (int, Integer)):
|
||||
diags = zip(*[(2*i, 2*i + 1) for i in range(expr.exp)])
|
||||
arr = _array_diagonal(_array_tensor_product(*[subexpr for i in range(expr.exp)]), *diags)
|
||||
return arr, subindices
|
||||
if isinstance(expr, Function):
|
||||
subexpr, subindices = _convert_indexed_to_array(expr.args[0])
|
||||
return ArrayElementwiseApplyFunc(type(expr), subexpr), subindices
|
||||
return expr, ()
|
||||
@@ -0,0 +1,87 @@
|
||||
from sympy import KroneckerProduct
|
||||
from sympy.core.basic import Basic
|
||||
from sympy.core.function import Lambda
|
||||
from sympy.core.mul import Mul
|
||||
from sympy.core.numbers import Integer
|
||||
from sympy.core.power import Pow
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.symbol import (Dummy, symbols)
|
||||
from sympy.matrices.expressions.hadamard import (HadamardPower, HadamardProduct)
|
||||
from sympy.matrices.expressions.matadd import MatAdd
|
||||
from sympy.matrices.expressions.matmul import MatMul
|
||||
from sympy.matrices.expressions.matpow import MatPow
|
||||
from sympy.matrices.expressions.trace import Trace
|
||||
from sympy.matrices.expressions.transpose import Transpose
|
||||
from sympy.matrices.expressions.matexpr import MatrixExpr
|
||||
from sympy.tensor.array.expressions.array_expressions import \
|
||||
ArrayElementwiseApplyFunc, _array_tensor_product, _array_contraction, \
|
||||
_array_diagonal, _array_add, _permute_dims, Reshape
|
||||
|
||||
|
||||
def convert_matrix_to_array(expr: Basic) -> Basic:
|
||||
if isinstance(expr, MatMul):
|
||||
args_nonmat = []
|
||||
args = []
|
||||
for arg in expr.args:
|
||||
if isinstance(arg, MatrixExpr):
|
||||
args.append(arg)
|
||||
else:
|
||||
args_nonmat.append(convert_matrix_to_array(arg))
|
||||
contractions = [(2*i+1, 2*i+2) for i in range(len(args)-1)]
|
||||
scalar = _array_tensor_product(*args_nonmat) if args_nonmat else S.One
|
||||
if scalar == 1:
|
||||
tprod = _array_tensor_product(
|
||||
*[convert_matrix_to_array(arg) for arg in args])
|
||||
else:
|
||||
tprod = _array_tensor_product(
|
||||
scalar,
|
||||
*[convert_matrix_to_array(arg) for arg in args])
|
||||
return _array_contraction(
|
||||
tprod,
|
||||
*contractions
|
||||
)
|
||||
elif isinstance(expr, MatAdd):
|
||||
return _array_add(
|
||||
*[convert_matrix_to_array(arg) for arg in expr.args]
|
||||
)
|
||||
elif isinstance(expr, Transpose):
|
||||
return _permute_dims(
|
||||
convert_matrix_to_array(expr.args[0]), [1, 0]
|
||||
)
|
||||
elif isinstance(expr, Trace):
|
||||
inner_expr: MatrixExpr = convert_matrix_to_array(expr.arg) # type: ignore
|
||||
return _array_contraction(inner_expr, (0, len(inner_expr.shape) - 1))
|
||||
elif isinstance(expr, Mul):
|
||||
return _array_tensor_product(*[convert_matrix_to_array(i) for i in expr.args])
|
||||
elif isinstance(expr, Pow):
|
||||
base = convert_matrix_to_array(expr.base)
|
||||
if (expr.exp > 0) == True:
|
||||
return _array_tensor_product(*[base for i in range(expr.exp)])
|
||||
else:
|
||||
return expr
|
||||
elif isinstance(expr, MatPow):
|
||||
base = convert_matrix_to_array(expr.base)
|
||||
if expr.exp.is_Integer != True:
|
||||
b = symbols("b", cls=Dummy)
|
||||
return ArrayElementwiseApplyFunc(Lambda(b, b**expr.exp), convert_matrix_to_array(base))
|
||||
elif (expr.exp > 0) == True:
|
||||
return convert_matrix_to_array(MatMul.fromiter(base for i in range(expr.exp)))
|
||||
else:
|
||||
return expr
|
||||
elif isinstance(expr, HadamardProduct):
|
||||
tp = _array_tensor_product(*[convert_matrix_to_array(arg) for arg in expr.args])
|
||||
diag = [[2*i for i in range(len(expr.args))], [2*i+1 for i in range(len(expr.args))]]
|
||||
return _array_diagonal(tp, *diag)
|
||||
elif isinstance(expr, HadamardPower):
|
||||
base, exp = expr.args
|
||||
if isinstance(exp, Integer) and exp > 0:
|
||||
return convert_matrix_to_array(HadamardProduct.fromiter(base for i in range(exp)))
|
||||
else:
|
||||
d = Dummy("d")
|
||||
return ArrayElementwiseApplyFunc(Lambda(d, d**exp), base)
|
||||
elif isinstance(expr, KroneckerProduct):
|
||||
kp_args = [convert_matrix_to_array(arg) for arg in expr.args]
|
||||
permutation = [2*i for i in range(len(kp_args))] + [2*i + 1 for i in range(len(kp_args))]
|
||||
return Reshape(_permute_dims(_array_tensor_product(*kp_args), permutation), expr.shape)
|
||||
else:
|
||||
return expr
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,808 @@
|
||||
import random
|
||||
|
||||
from sympy import tensordiagonal, eye, KroneckerDelta, Array
|
||||
from sympy.core.symbol import symbols
|
||||
from sympy.functions.elementary.trigonometric import (cos, sin)
|
||||
from sympy.matrices.expressions.diagonal import DiagMatrix
|
||||
from sympy.matrices.expressions.matexpr import MatrixSymbol
|
||||
from sympy.matrices.expressions.special import ZeroMatrix
|
||||
from sympy.tensor.array.arrayop import (permutedims, tensorcontraction, tensorproduct)
|
||||
from sympy.tensor.array.dense_ndim_array import ImmutableDenseNDimArray
|
||||
from sympy.combinatorics import Permutation
|
||||
from sympy.tensor.array.expressions.array_expressions import ZeroArray, OneArray, ArraySymbol, ArrayElement, \
|
||||
PermuteDims, ArrayContraction, ArrayTensorProduct, ArrayDiagonal, \
|
||||
ArrayAdd, nest_permutation, ArrayElementwiseApplyFunc, _EditArrayContraction, _ArgE, _array_tensor_product, \
|
||||
_array_contraction, _array_diagonal, _array_add, _permute_dims, Reshape
|
||||
from sympy.testing.pytest import raises
|
||||
|
||||
i, j, k, l, m, n = symbols("i j k l m n")
|
||||
|
||||
|
||||
M = ArraySymbol("M", (k, k))
|
||||
N = ArraySymbol("N", (k, k))
|
||||
P = ArraySymbol("P", (k, k))
|
||||
Q = ArraySymbol("Q", (k, k))
|
||||
|
||||
A = ArraySymbol("A", (k, k))
|
||||
B = ArraySymbol("B", (k, k))
|
||||
C = ArraySymbol("C", (k, k))
|
||||
D = ArraySymbol("D", (k, k))
|
||||
|
||||
X = ArraySymbol("X", (k, k))
|
||||
Y = ArraySymbol("Y", (k, k))
|
||||
|
||||
a = ArraySymbol("a", (k, 1))
|
||||
b = ArraySymbol("b", (k, 1))
|
||||
c = ArraySymbol("c", (k, 1))
|
||||
d = ArraySymbol("d", (k, 1))
|
||||
|
||||
|
||||
def test_array_symbol_and_element():
|
||||
A = ArraySymbol("A", (2,))
|
||||
A0 = ArrayElement(A, (0,))
|
||||
A1 = ArrayElement(A, (1,))
|
||||
assert A[0] == A0
|
||||
assert A[1] != A0
|
||||
assert A.as_explicit() == ImmutableDenseNDimArray([A0, A1])
|
||||
|
||||
A2 = tensorproduct(A, A)
|
||||
assert A2.shape == (2, 2)
|
||||
# TODO: not yet supported:
|
||||
# assert A2.as_explicit() == Array([[A[0]*A[0], A[1]*A[0]], [A[0]*A[1], A[1]*A[1]]])
|
||||
A3 = tensorcontraction(A2, (0, 1))
|
||||
assert A3.shape == ()
|
||||
# TODO: not yet supported:
|
||||
# assert A3.as_explicit() == Array([])
|
||||
|
||||
A = ArraySymbol("A", (2, 3, 4))
|
||||
Ae = A.as_explicit()
|
||||
assert Ae == ImmutableDenseNDimArray(
|
||||
[[[ArrayElement(A, (i, j, k)) for k in range(4)] for j in range(3)] for i in range(2)])
|
||||
|
||||
p = _permute_dims(A, Permutation(0, 2, 1))
|
||||
assert isinstance(p, PermuteDims)
|
||||
|
||||
A = ArraySymbol("A", (2,))
|
||||
raises(IndexError, lambda: A[()])
|
||||
raises(IndexError, lambda: A[0, 1])
|
||||
raises(ValueError, lambda: A[-1])
|
||||
raises(ValueError, lambda: A[2])
|
||||
|
||||
O = OneArray(3, 4)
|
||||
Z = ZeroArray(m, n)
|
||||
|
||||
raises(IndexError, lambda: O[()])
|
||||
raises(IndexError, lambda: O[1, 2, 3])
|
||||
raises(ValueError, lambda: O[3, 0])
|
||||
raises(ValueError, lambda: O[0, 4])
|
||||
|
||||
assert O[1, 2] == 1
|
||||
assert Z[1, 2] == 0
|
||||
|
||||
|
||||
def test_zero_array():
|
||||
assert ZeroArray() == 0
|
||||
assert ZeroArray().is_Integer
|
||||
|
||||
za = ZeroArray(3, 2, 4)
|
||||
assert za.shape == (3, 2, 4)
|
||||
za_e = za.as_explicit()
|
||||
assert za_e.shape == (3, 2, 4)
|
||||
|
||||
m, n, k = symbols("m n k")
|
||||
za = ZeroArray(m, n, k, 2)
|
||||
assert za.shape == (m, n, k, 2)
|
||||
raises(ValueError, lambda: za.as_explicit())
|
||||
|
||||
|
||||
def test_one_array():
|
||||
assert OneArray() == 1
|
||||
assert OneArray().is_Integer
|
||||
|
||||
oa = OneArray(3, 2, 4)
|
||||
assert oa.shape == (3, 2, 4)
|
||||
oa_e = oa.as_explicit()
|
||||
assert oa_e.shape == (3, 2, 4)
|
||||
|
||||
m, n, k = symbols("m n k")
|
||||
oa = OneArray(m, n, k, 2)
|
||||
assert oa.shape == (m, n, k, 2)
|
||||
raises(ValueError, lambda: oa.as_explicit())
|
||||
|
||||
|
||||
def test_arrayexpr_contraction_construction():
|
||||
|
||||
cg = _array_contraction(A)
|
||||
assert cg == A
|
||||
|
||||
cg = _array_contraction(_array_tensor_product(A, B), (1, 0))
|
||||
assert cg == _array_contraction(_array_tensor_product(A, B), (0, 1))
|
||||
|
||||
cg = _array_contraction(_array_tensor_product(M, N), (0, 1))
|
||||
indtup = cg._get_contraction_tuples()
|
||||
assert indtup == [[(0, 0), (0, 1)]]
|
||||
assert cg._contraction_tuples_to_contraction_indices(cg.expr, indtup) == [(0, 1)]
|
||||
|
||||
cg = _array_contraction(_array_tensor_product(M, N), (1, 2))
|
||||
indtup = cg._get_contraction_tuples()
|
||||
assert indtup == [[(0, 1), (1, 0)]]
|
||||
assert cg._contraction_tuples_to_contraction_indices(cg.expr, indtup) == [(1, 2)]
|
||||
|
||||
cg = _array_contraction(_array_tensor_product(M, M, N), (1, 4), (2, 5))
|
||||
indtup = cg._get_contraction_tuples()
|
||||
assert indtup == [[(0, 0), (1, 1)], [(0, 1), (2, 0)]]
|
||||
assert cg._contraction_tuples_to_contraction_indices(cg.expr, indtup) == [(0, 3), (1, 4)]
|
||||
|
||||
# Test removal of trivial contraction:
|
||||
assert _array_contraction(a, (1,)) == a
|
||||
assert _array_contraction(
|
||||
_array_tensor_product(a, b), (0, 2), (1,), (3,)) == _array_contraction(
|
||||
_array_tensor_product(a, b), (0, 2))
|
||||
|
||||
|
||||
def test_arrayexpr_array_flatten():
|
||||
|
||||
# Flatten nested ArrayTensorProduct objects:
|
||||
expr1 = _array_tensor_product(M, N)
|
||||
expr2 = _array_tensor_product(P, Q)
|
||||
expr = _array_tensor_product(expr1, expr2)
|
||||
assert expr == _array_tensor_product(M, N, P, Q)
|
||||
assert expr.args == (M, N, P, Q)
|
||||
|
||||
# Flatten mixed ArrayTensorProduct and ArrayContraction objects:
|
||||
cg1 = _array_contraction(expr1, (1, 2))
|
||||
cg2 = _array_contraction(expr2, (0, 3))
|
||||
|
||||
expr = _array_tensor_product(cg1, cg2)
|
||||
assert expr == _array_contraction(_array_tensor_product(M, N, P, Q), (1, 2), (4, 7))
|
||||
|
||||
expr = _array_tensor_product(M, cg1)
|
||||
assert expr == _array_contraction(_array_tensor_product(M, M, N), (3, 4))
|
||||
|
||||
# Flatten nested ArrayContraction objects:
|
||||
cgnested = _array_contraction(cg1, (0, 1))
|
||||
assert cgnested == _array_contraction(_array_tensor_product(M, N), (0, 3), (1, 2))
|
||||
|
||||
cgnested = _array_contraction(_array_tensor_product(cg1, cg2), (0, 3))
|
||||
assert cgnested == _array_contraction(_array_tensor_product(M, N, P, Q), (0, 6), (1, 2), (4, 7))
|
||||
|
||||
cg3 = _array_contraction(_array_tensor_product(M, N, P, Q), (1, 3), (2, 4))
|
||||
cgnested = _array_contraction(cg3, (0, 1))
|
||||
assert cgnested == _array_contraction(_array_tensor_product(M, N, P, Q), (0, 5), (1, 3), (2, 4))
|
||||
|
||||
cgnested = _array_contraction(cg3, (0, 3), (1, 2))
|
||||
assert cgnested == _array_contraction(_array_tensor_product(M, N, P, Q), (0, 7), (1, 3), (2, 4), (5, 6))
|
||||
|
||||
cg4 = _array_contraction(_array_tensor_product(M, N, P, Q), (1, 5), (3, 7))
|
||||
cgnested = _array_contraction(cg4, (0, 1))
|
||||
assert cgnested == _array_contraction(_array_tensor_product(M, N, P, Q), (0, 2), (1, 5), (3, 7))
|
||||
|
||||
cgnested = _array_contraction(cg4, (0, 1), (2, 3))
|
||||
assert cgnested == _array_contraction(_array_tensor_product(M, N, P, Q), (0, 2), (1, 5), (3, 7), (4, 6))
|
||||
|
||||
cg = _array_diagonal(cg4)
|
||||
assert cg == cg4
|
||||
assert isinstance(cg, type(cg4))
|
||||
|
||||
# Flatten nested ArrayDiagonal objects:
|
||||
cg1 = _array_diagonal(expr1, (1, 2))
|
||||
cg2 = _array_diagonal(expr2, (0, 3))
|
||||
cg3 = _array_diagonal(_array_tensor_product(M, N, P, Q), (1, 3), (2, 4))
|
||||
cg4 = _array_diagonal(_array_tensor_product(M, N, P, Q), (1, 5), (3, 7))
|
||||
|
||||
cgnested = _array_diagonal(cg1, (0, 1))
|
||||
assert cgnested == _array_diagonal(_array_tensor_product(M, N), (1, 2), (0, 3))
|
||||
|
||||
cgnested = _array_diagonal(cg3, (1, 2))
|
||||
assert cgnested == _array_diagonal(_array_tensor_product(M, N, P, Q), (1, 3), (2, 4), (5, 6))
|
||||
|
||||
cgnested = _array_diagonal(cg4, (1, 2))
|
||||
assert cgnested == _array_diagonal(_array_tensor_product(M, N, P, Q), (1, 5), (3, 7), (2, 4))
|
||||
|
||||
cg = _array_add(M, N)
|
||||
cg2 = _array_add(cg, P)
|
||||
assert isinstance(cg2, ArrayAdd)
|
||||
assert cg2.args == (M, N, P)
|
||||
assert cg2.shape == (k, k)
|
||||
|
||||
expr = _array_tensor_product(_array_diagonal(X, (0, 1)), _array_diagonal(A, (0, 1)))
|
||||
assert expr == _array_diagonal(_array_tensor_product(X, A), (0, 1), (2, 3))
|
||||
|
||||
expr1 = _array_diagonal(_array_tensor_product(X, A), (1, 2))
|
||||
expr2 = _array_tensor_product(expr1, a)
|
||||
assert expr2 == _permute_dims(_array_diagonal(_array_tensor_product(X, A, a), (1, 2)), [0, 1, 4, 2, 3])
|
||||
|
||||
expr1 = _array_contraction(_array_tensor_product(X, A), (1, 2))
|
||||
expr2 = _array_tensor_product(expr1, a)
|
||||
assert isinstance(expr2, ArrayContraction)
|
||||
assert isinstance(expr2.expr, ArrayTensorProduct)
|
||||
|
||||
cg = _array_tensor_product(_array_diagonal(_array_tensor_product(A, X, Y), (0, 3), (1, 5)), a, b)
|
||||
assert cg == _permute_dims(_array_diagonal(_array_tensor_product(A, X, Y, a, b), (0, 3), (1, 5)), [0, 1, 6, 7, 2, 3, 4, 5])
|
||||
|
||||
|
||||
def test_arrayexpr_array_diagonal():
|
||||
cg = _array_diagonal(M, (1, 0))
|
||||
assert cg == _array_diagonal(M, (0, 1))
|
||||
|
||||
cg = _array_diagonal(_array_tensor_product(M, N, P), (4, 1), (2, 0))
|
||||
assert cg == _array_diagonal(_array_tensor_product(M, N, P), (1, 4), (0, 2))
|
||||
|
||||
cg = _array_diagonal(_array_tensor_product(M, N), (1, 2), (3,), allow_trivial_diags=True)
|
||||
assert cg == _permute_dims(_array_diagonal(_array_tensor_product(M, N), (1, 2)), [0, 2, 1])
|
||||
|
||||
Ax = ArraySymbol("Ax", shape=(1, 2, 3, 4, 3, 5, 6, 2, 7))
|
||||
cg = _array_diagonal(Ax, (1, 7), (3,), (2, 4), (6,), allow_trivial_diags=True)
|
||||
assert cg == _permute_dims(_array_diagonal(Ax, (1, 7), (2, 4)), [0, 2, 4, 5, 1, 6, 3])
|
||||
|
||||
cg = _array_diagonal(M, (0,), allow_trivial_diags=True)
|
||||
assert cg == _permute_dims(M, [1, 0])
|
||||
|
||||
raises(ValueError, lambda: _array_diagonal(M, (0, 0)))
|
||||
|
||||
|
||||
def test_arrayexpr_array_shape():
|
||||
expr = _array_tensor_product(M, N, P, Q)
|
||||
assert expr.shape == (k, k, k, k, k, k, k, k)
|
||||
Z = MatrixSymbol("Z", m, n)
|
||||
expr = _array_tensor_product(M, Z)
|
||||
assert expr.shape == (k, k, m, n)
|
||||
expr2 = _array_contraction(expr, (0, 1))
|
||||
assert expr2.shape == (m, n)
|
||||
expr2 = _array_diagonal(expr, (0, 1))
|
||||
assert expr2.shape == (m, n, k)
|
||||
exprp = _permute_dims(expr, [2, 1, 3, 0])
|
||||
assert exprp.shape == (m, k, n, k)
|
||||
expr3 = _array_tensor_product(N, Z)
|
||||
expr2 = _array_add(expr, expr3)
|
||||
assert expr2.shape == (k, k, m, n)
|
||||
|
||||
# Contraction along axes with discordant dimensions:
|
||||
raises(ValueError, lambda: _array_contraction(expr, (1, 2)))
|
||||
# Also diagonal needs the same dimensions:
|
||||
raises(ValueError, lambda: _array_diagonal(expr, (1, 2)))
|
||||
# Diagonal requires at least to axes to compute the diagonal:
|
||||
raises(ValueError, lambda: _array_diagonal(expr, (1,)))
|
||||
|
||||
|
||||
def test_arrayexpr_permutedims_sink():
|
||||
|
||||
cg = _permute_dims(_array_tensor_product(M, N), [0, 1, 3, 2], nest_permutation=False)
|
||||
sunk = nest_permutation(cg)
|
||||
assert sunk == _array_tensor_product(M, _permute_dims(N, [1, 0]))
|
||||
|
||||
cg = _permute_dims(_array_tensor_product(M, N), [1, 0, 3, 2], nest_permutation=False)
|
||||
sunk = nest_permutation(cg)
|
||||
assert sunk == _array_tensor_product(_permute_dims(M, [1, 0]), _permute_dims(N, [1, 0]))
|
||||
|
||||
cg = _permute_dims(_array_tensor_product(M, N), [3, 2, 1, 0], nest_permutation=False)
|
||||
sunk = nest_permutation(cg)
|
||||
assert sunk == _array_tensor_product(_permute_dims(N, [1, 0]), _permute_dims(M, [1, 0]))
|
||||
|
||||
cg = _permute_dims(_array_contraction(_array_tensor_product(M, N), (1, 2)), [1, 0], nest_permutation=False)
|
||||
sunk = nest_permutation(cg)
|
||||
assert sunk == _array_contraction(_permute_dims(_array_tensor_product(M, N), [[0, 3]]), (1, 2))
|
||||
|
||||
cg = _permute_dims(_array_tensor_product(M, N), [1, 0, 3, 2], nest_permutation=False)
|
||||
sunk = nest_permutation(cg)
|
||||
assert sunk == _array_tensor_product(_permute_dims(M, [1, 0]), _permute_dims(N, [1, 0]))
|
||||
|
||||
cg = _permute_dims(_array_contraction(_array_tensor_product(M, N, P), (1, 2), (3, 4)), [1, 0], nest_permutation=False)
|
||||
sunk = nest_permutation(cg)
|
||||
assert sunk == _array_contraction(_permute_dims(_array_tensor_product(M, N, P), [[0, 5]]), (1, 2), (3, 4))
|
||||
|
||||
|
||||
def test_arrayexpr_push_indices_up_and_down():
|
||||
|
||||
indices = list(range(12))
|
||||
|
||||
contr_diag_indices = [(0, 6), (2, 8)]
|
||||
assert ArrayContraction._push_indices_down(contr_diag_indices, indices) == (1, 3, 4, 5, 7, 9, 10, 11, 12, 13, 14, 15)
|
||||
assert ArrayContraction._push_indices_up(contr_diag_indices, indices) == (None, 0, None, 1, 2, 3, None, 4, None, 5, 6, 7)
|
||||
|
||||
assert ArrayDiagonal._push_indices_down(contr_diag_indices, indices, 10) == (1, 3, 4, 5, 7, 9, (0, 6), (2, 8), None, None, None, None)
|
||||
assert ArrayDiagonal._push_indices_up(contr_diag_indices, indices, 10) == (6, 0, 7, 1, 2, 3, 6, 4, 7, 5, None, None)
|
||||
|
||||
contr_diag_indices = [(1, 2), (7, 8)]
|
||||
assert ArrayContraction._push_indices_down(contr_diag_indices, indices) == (0, 3, 4, 5, 6, 9, 10, 11, 12, 13, 14, 15)
|
||||
assert ArrayContraction._push_indices_up(contr_diag_indices, indices) == (0, None, None, 1, 2, 3, 4, None, None, 5, 6, 7)
|
||||
|
||||
assert ArrayDiagonal._push_indices_down(contr_diag_indices, indices, 10) == (0, 3, 4, 5, 6, 9, (1, 2), (7, 8), None, None, None, None)
|
||||
assert ArrayDiagonal._push_indices_up(contr_diag_indices, indices, 10) == (0, 6, 6, 1, 2, 3, 4, 7, 7, 5, None, None)
|
||||
|
||||
|
||||
def test_arrayexpr_split_multiple_contractions():
|
||||
a = MatrixSymbol("a", k, 1)
|
||||
b = MatrixSymbol("b", k, 1)
|
||||
A = MatrixSymbol("A", k, k)
|
||||
B = MatrixSymbol("B", k, k)
|
||||
C = MatrixSymbol("C", k, k)
|
||||
X = MatrixSymbol("X", k, k)
|
||||
|
||||
cg = _array_contraction(_array_tensor_product(A.T, a, b, b.T, (A*X*b).applyfunc(cos)), (1, 2, 8), (5, 6, 9))
|
||||
expected = _array_contraction(_array_tensor_product(A.T, DiagMatrix(a), OneArray(1), b, b.T, (A*X*b).applyfunc(cos)), (1, 3), (2, 9), (6, 7, 10))
|
||||
assert cg.split_multiple_contractions().dummy_eq(expected)
|
||||
|
||||
# Check no overlap of lines:
|
||||
|
||||
cg = _array_contraction(_array_tensor_product(A, a, C, a, B), (1, 2, 4), (5, 6, 8), (3, 7))
|
||||
assert cg.split_multiple_contractions() == cg
|
||||
|
||||
cg = _array_contraction(_array_tensor_product(a, b, A), (0, 2, 4), (1, 3))
|
||||
assert cg.split_multiple_contractions() == cg
|
||||
|
||||
|
||||
def test_arrayexpr_nested_permutations():
|
||||
|
||||
cg = _permute_dims(_permute_dims(M, (1, 0)), (1, 0))
|
||||
assert cg == M
|
||||
|
||||
times = 3
|
||||
plist1 = [list(range(6)) for i in range(times)]
|
||||
plist2 = [list(range(6)) for i in range(times)]
|
||||
|
||||
for i in range(times):
|
||||
random.shuffle(plist1[i])
|
||||
random.shuffle(plist2[i])
|
||||
|
||||
plist1.append([2, 5, 4, 1, 0, 3])
|
||||
plist2.append([3, 5, 0, 4, 1, 2])
|
||||
|
||||
plist1.append([2, 5, 4, 0, 3, 1])
|
||||
plist2.append([3, 0, 5, 1, 2, 4])
|
||||
|
||||
plist1.append([5, 4, 2, 0, 3, 1])
|
||||
plist2.append([4, 5, 0, 2, 3, 1])
|
||||
|
||||
Me = M.subs(k, 3).as_explicit()
|
||||
Ne = N.subs(k, 3).as_explicit()
|
||||
Pe = P.subs(k, 3).as_explicit()
|
||||
cge = tensorproduct(Me, Ne, Pe)
|
||||
|
||||
for permutation_array1, permutation_array2 in zip(plist1, plist2):
|
||||
p1 = Permutation(permutation_array1)
|
||||
p2 = Permutation(permutation_array2)
|
||||
|
||||
cg = _permute_dims(
|
||||
_permute_dims(
|
||||
_array_tensor_product(M, N, P),
|
||||
p1),
|
||||
p2
|
||||
)
|
||||
result = _permute_dims(
|
||||
_array_tensor_product(M, N, P),
|
||||
p2*p1
|
||||
)
|
||||
assert cg == result
|
||||
|
||||
# Check that `permutedims` behaves the same way with explicit-component arrays:
|
||||
result1 = _permute_dims(_permute_dims(cge, p1), p2)
|
||||
result2 = _permute_dims(cge, p2*p1)
|
||||
assert result1 == result2
|
||||
|
||||
|
||||
def test_arrayexpr_contraction_permutation_mix():
|
||||
|
||||
Me = M.subs(k, 3).as_explicit()
|
||||
Ne = N.subs(k, 3).as_explicit()
|
||||
|
||||
cg1 = _array_contraction(PermuteDims(_array_tensor_product(M, N), Permutation([0, 2, 1, 3])), (2, 3))
|
||||
cg2 = _array_contraction(_array_tensor_product(M, N), (1, 3))
|
||||
assert cg1 == cg2
|
||||
cge1 = tensorcontraction(permutedims(tensorproduct(Me, Ne), Permutation([0, 2, 1, 3])), (2, 3))
|
||||
cge2 = tensorcontraction(tensorproduct(Me, Ne), (1, 3))
|
||||
assert cge1 == cge2
|
||||
|
||||
cg1 = _permute_dims(_array_tensor_product(M, N), Permutation([0, 1, 3, 2]))
|
||||
cg2 = _array_tensor_product(M, _permute_dims(N, Permutation([1, 0])))
|
||||
assert cg1 == cg2
|
||||
|
||||
cg1 = _array_contraction(
|
||||
_permute_dims(
|
||||
_array_tensor_product(M, N, P, Q), Permutation([0, 2, 3, 1, 4, 5, 7, 6])),
|
||||
(1, 2), (3, 5)
|
||||
)
|
||||
cg2 = _array_contraction(
|
||||
_array_tensor_product(M, N, P, _permute_dims(Q, Permutation([1, 0]))),
|
||||
(1, 5), (2, 3)
|
||||
)
|
||||
assert cg1 == cg2
|
||||
|
||||
cg1 = _array_contraction(
|
||||
_permute_dims(
|
||||
_array_tensor_product(M, N, P, Q), Permutation([1, 0, 4, 6, 2, 7, 5, 3])),
|
||||
(0, 1), (2, 6), (3, 7)
|
||||
)
|
||||
cg2 = _permute_dims(
|
||||
_array_contraction(
|
||||
_array_tensor_product(M, P, Q, N),
|
||||
(0, 1), (2, 3), (4, 7)),
|
||||
[1, 0]
|
||||
)
|
||||
assert cg1 == cg2
|
||||
|
||||
cg1 = _array_contraction(
|
||||
_permute_dims(
|
||||
_array_tensor_product(M, N, P, Q), Permutation([1, 0, 4, 6, 7, 2, 5, 3])),
|
||||
(0, 1), (2, 6), (3, 7)
|
||||
)
|
||||
cg2 = _permute_dims(
|
||||
_array_contraction(
|
||||
_array_tensor_product(_permute_dims(M, [1, 0]), N, P, Q),
|
||||
(0, 1), (3, 6), (4, 5)
|
||||
),
|
||||
Permutation([1, 0])
|
||||
)
|
||||
assert cg1 == cg2
|
||||
|
||||
|
||||
def test_arrayexpr_permute_tensor_product():
|
||||
cg1 = _permute_dims(_array_tensor_product(M, N, P, Q), Permutation([2, 3, 1, 0, 5, 4, 6, 7]))
|
||||
cg2 = _array_tensor_product(N, _permute_dims(M, [1, 0]),
|
||||
_permute_dims(P, [1, 0]), Q)
|
||||
assert cg1 == cg2
|
||||
|
||||
# TODO: reverse operation starting with `PermuteDims` and getting down to `bb`...
|
||||
cg1 = _permute_dims(_array_tensor_product(M, N, P, Q), Permutation([2, 3, 4, 5, 0, 1, 6, 7]))
|
||||
cg2 = _array_tensor_product(N, P, M, Q)
|
||||
assert cg1 == cg2
|
||||
|
||||
cg1 = _permute_dims(_array_tensor_product(M, N, P, Q), Permutation([2, 3, 4, 6, 5, 7, 0, 1]))
|
||||
assert cg1.expr == _array_tensor_product(N, P, Q, M)
|
||||
assert cg1.permutation == Permutation([0, 1, 2, 4, 3, 5, 6, 7])
|
||||
|
||||
cg1 = _array_contraction(
|
||||
_permute_dims(
|
||||
_array_tensor_product(N, Q, Q, M),
|
||||
[2, 1, 5, 4, 0, 3, 6, 7]),
|
||||
[1, 2, 6])
|
||||
cg2 = _permute_dims(_array_contraction(_array_tensor_product(Q, Q, N, M), (3, 5, 6)), [0, 2, 3, 1, 4])
|
||||
assert cg1 == cg2
|
||||
|
||||
cg1 = _array_contraction(
|
||||
_array_contraction(
|
||||
_array_contraction(
|
||||
_array_contraction(
|
||||
_permute_dims(
|
||||
_array_tensor_product(N, Q, Q, M),
|
||||
[2, 1, 5, 4, 0, 3, 6, 7]),
|
||||
[1, 2, 6]),
|
||||
[1, 3, 4]),
|
||||
[1]),
|
||||
[0])
|
||||
cg2 = _array_contraction(_array_tensor_product(M, N, Q, Q), (0, 3, 5), (1, 4, 7), (2,), (6,))
|
||||
assert cg1 == cg2
|
||||
|
||||
|
||||
def test_arrayexpr_canonicalize_diagonal__permute_dims():
|
||||
tp = _array_tensor_product(M, Q, N, P)
|
||||
expr = _array_diagonal(
|
||||
_permute_dims(tp, [0, 1, 2, 4, 7, 6, 3, 5]), (2, 4, 5), (6, 7),
|
||||
(0, 3))
|
||||
result = _array_diagonal(tp, (2, 6, 7), (3, 5), (0, 4))
|
||||
assert expr == result
|
||||
|
||||
tp = _array_tensor_product(M, N, P, Q)
|
||||
expr = _array_diagonal(_permute_dims(tp, [0, 5, 2, 4, 1, 6, 3, 7]), (1, 2, 6), (3, 4))
|
||||
result = _array_diagonal(_array_tensor_product(M, P, N, Q), (3, 4, 5), (1, 2))
|
||||
assert expr == result
|
||||
|
||||
|
||||
def test_arrayexpr_canonicalize_diagonal_contraction():
|
||||
tp = _array_tensor_product(M, N, P, Q)
|
||||
expr = _array_contraction(_array_diagonal(tp, (1, 3, 4)), (0, 3))
|
||||
result = _array_diagonal(_array_contraction(_array_tensor_product(M, N, P, Q), (0, 6)), (0, 2, 3))
|
||||
assert expr == result
|
||||
|
||||
expr = _array_contraction(_array_diagonal(tp, (0, 1, 2, 3, 7)), (1, 2, 3))
|
||||
result = _array_contraction(_array_tensor_product(M, N, P, Q), (0, 1, 2, 3, 5, 6, 7))
|
||||
assert expr == result
|
||||
|
||||
expr = _array_contraction(_array_diagonal(tp, (0, 2, 6, 7)), (1, 2, 3))
|
||||
result = _array_diagonal(_array_contraction(tp, (3, 4, 5)), (0, 2, 3, 4))
|
||||
assert expr == result
|
||||
|
||||
td = _array_diagonal(_array_tensor_product(M, N, P, Q), (0, 3))
|
||||
expr = _array_contraction(td, (2, 1), (0, 4, 6, 5, 3))
|
||||
result = _array_contraction(_array_tensor_product(M, N, P, Q), (0, 1, 3, 5, 6, 7), (2, 4))
|
||||
assert expr == result
|
||||
|
||||
|
||||
def test_arrayexpr_array_wrong_permutation_size():
|
||||
cg = _array_tensor_product(M, N)
|
||||
raises(ValueError, lambda: _permute_dims(cg, [1, 0]))
|
||||
raises(ValueError, lambda: _permute_dims(cg, [1, 0, 2, 3, 5, 4]))
|
||||
|
||||
|
||||
def test_arrayexpr_nested_array_elementwise_add():
|
||||
cg = _array_contraction(_array_add(
|
||||
_array_tensor_product(M, N),
|
||||
_array_tensor_product(N, M)
|
||||
), (1, 2))
|
||||
result = _array_add(
|
||||
_array_contraction(_array_tensor_product(M, N), (1, 2)),
|
||||
_array_contraction(_array_tensor_product(N, M), (1, 2))
|
||||
)
|
||||
assert cg == result
|
||||
|
||||
cg = _array_diagonal(_array_add(
|
||||
_array_tensor_product(M, N),
|
||||
_array_tensor_product(N, M)
|
||||
), (1, 2))
|
||||
result = _array_add(
|
||||
_array_diagonal(_array_tensor_product(M, N), (1, 2)),
|
||||
_array_diagonal(_array_tensor_product(N, M), (1, 2))
|
||||
)
|
||||
assert cg == result
|
||||
|
||||
|
||||
def test_arrayexpr_array_expr_zero_array():
|
||||
za1 = ZeroArray(k, l, m, n)
|
||||
zm1 = ZeroMatrix(m, n)
|
||||
|
||||
za2 = ZeroArray(k, m, m, n)
|
||||
zm2 = ZeroMatrix(m, m)
|
||||
zm3 = ZeroMatrix(k, k)
|
||||
|
||||
assert _array_tensor_product(M, N, za1) == ZeroArray(k, k, k, k, k, l, m, n)
|
||||
assert _array_tensor_product(M, N, zm1) == ZeroArray(k, k, k, k, m, n)
|
||||
|
||||
assert _array_contraction(za1, (3,)) == ZeroArray(k, l, m)
|
||||
assert _array_contraction(zm1, (1,)) == ZeroArray(m)
|
||||
assert _array_contraction(za2, (1, 2)) == ZeroArray(k, n)
|
||||
assert _array_contraction(zm2, (0, 1)) == 0
|
||||
|
||||
assert _array_diagonal(za2, (1, 2)) == ZeroArray(k, n, m)
|
||||
assert _array_diagonal(zm2, (0, 1)) == ZeroArray(m)
|
||||
|
||||
assert _permute_dims(za1, [2, 1, 3, 0]) == ZeroArray(m, l, n, k)
|
||||
assert _permute_dims(zm1, [1, 0]) == ZeroArray(n, m)
|
||||
|
||||
assert _array_add(za1) == za1
|
||||
assert _array_add(zm1) == ZeroArray(m, n)
|
||||
tp1 = _array_tensor_product(MatrixSymbol("A", k, l), MatrixSymbol("B", m, n))
|
||||
assert _array_add(tp1, za1) == tp1
|
||||
tp2 = _array_tensor_product(MatrixSymbol("C", k, l), MatrixSymbol("D", m, n))
|
||||
assert _array_add(tp1, za1, tp2) == _array_add(tp1, tp2)
|
||||
assert _array_add(M, zm3) == M
|
||||
assert _array_add(M, N, zm3) == _array_add(M, N)
|
||||
|
||||
|
||||
def test_arrayexpr_array_expr_applyfunc():
|
||||
|
||||
A = ArraySymbol("A", (3, k, 2))
|
||||
aaf = ArrayElementwiseApplyFunc(sin, A)
|
||||
assert aaf.shape == (3, k, 2)
|
||||
|
||||
|
||||
def test_edit_array_contraction():
|
||||
cg = _array_contraction(_array_tensor_product(A, B, C, D), (1, 2, 5))
|
||||
ecg = _EditArrayContraction(cg)
|
||||
assert ecg.to_array_contraction() == cg
|
||||
|
||||
ecg.args_with_ind[1], ecg.args_with_ind[2] = ecg.args_with_ind[2], ecg.args_with_ind[1]
|
||||
assert ecg.to_array_contraction() == _array_contraction(_array_tensor_product(A, C, B, D), (1, 3, 4))
|
||||
|
||||
ci = ecg.get_new_contraction_index()
|
||||
new_arg = _ArgE(X)
|
||||
new_arg.indices = [ci, ci]
|
||||
ecg.args_with_ind.insert(2, new_arg)
|
||||
assert ecg.to_array_contraction() == _array_contraction(_array_tensor_product(A, C, X, B, D), (1, 3, 6), (4, 5))
|
||||
|
||||
assert ecg.get_contraction_indices() == [[1, 3, 6], [4, 5]]
|
||||
assert [[tuple(j) for j in i] for i in ecg.get_contraction_indices_to_ind_rel_pos()] == [[(0, 1), (1, 1), (3, 0)], [(2, 0), (2, 1)]]
|
||||
assert [list(i) for i in ecg.get_mapping_for_index(0)] == [[0, 1], [1, 1], [3, 0]]
|
||||
assert [list(i) for i in ecg.get_mapping_for_index(1)] == [[2, 0], [2, 1]]
|
||||
raises(ValueError, lambda: ecg.get_mapping_for_index(2))
|
||||
|
||||
ecg.args_with_ind.pop(1)
|
||||
assert ecg.to_array_contraction() == _array_contraction(_array_tensor_product(A, X, B, D), (1, 4), (2, 3))
|
||||
|
||||
ecg.args_with_ind[0].indices[1] = ecg.args_with_ind[1].indices[0]
|
||||
ecg.args_with_ind[1].indices[1] = ecg.args_with_ind[2].indices[0]
|
||||
assert ecg.to_array_contraction() == _array_contraction(_array_tensor_product(A, X, B, D), (1, 2), (3, 4))
|
||||
|
||||
ecg.insert_after(ecg.args_with_ind[1], _ArgE(C))
|
||||
assert ecg.to_array_contraction() == _array_contraction(_array_tensor_product(A, X, C, B, D), (1, 2), (3, 6))
|
||||
|
||||
|
||||
def test_array_expressions_no_canonicalization():
|
||||
|
||||
tp = _array_tensor_product(M, N, P)
|
||||
|
||||
# ArrayTensorProduct:
|
||||
|
||||
expr = ArrayTensorProduct(tp, N)
|
||||
assert str(expr) == "ArrayTensorProduct(ArrayTensorProduct(M, N, P), N)"
|
||||
assert expr.doit() == ArrayTensorProduct(M, N, P, N)
|
||||
|
||||
expr = ArrayTensorProduct(ArrayContraction(M, (0, 1)), N)
|
||||
assert str(expr) == "ArrayTensorProduct(ArrayContraction(M, (0, 1)), N)"
|
||||
assert expr.doit() == ArrayContraction(ArrayTensorProduct(M, N), (0, 1))
|
||||
|
||||
expr = ArrayTensorProduct(ArrayDiagonal(M, (0, 1)), N)
|
||||
assert str(expr) == "ArrayTensorProduct(ArrayDiagonal(M, (0, 1)), N)"
|
||||
assert expr.doit() == PermuteDims(ArrayDiagonal(ArrayTensorProduct(M, N), (0, 1)), [2, 0, 1])
|
||||
|
||||
expr = ArrayTensorProduct(PermuteDims(M, [1, 0]), N)
|
||||
assert str(expr) == "ArrayTensorProduct(PermuteDims(M, (0 1)), N)"
|
||||
assert expr.doit() == PermuteDims(ArrayTensorProduct(M, N), [1, 0, 2, 3])
|
||||
|
||||
# ArrayContraction:
|
||||
|
||||
expr = ArrayContraction(_array_contraction(tp, (0, 2)), (0, 1))
|
||||
assert isinstance(expr, ArrayContraction)
|
||||
assert isinstance(expr.expr, ArrayContraction)
|
||||
assert str(expr) == "ArrayContraction(ArrayContraction(ArrayTensorProduct(M, N, P), (0, 2)), (0, 1))"
|
||||
assert expr.doit() == ArrayContraction(tp, (0, 2), (1, 3))
|
||||
|
||||
expr = ArrayContraction(ArrayContraction(ArrayContraction(tp, (0, 1)), (0, 1)), (0, 1))
|
||||
assert expr.doit() == ArrayContraction(tp, (0, 1), (2, 3), (4, 5))
|
||||
# assert expr._canonicalize() == ArrayContraction(ArrayContraction(tp, (0, 1)), (0, 1), (2, 3))
|
||||
|
||||
expr = ArrayContraction(ArrayDiagonal(tp, (0, 1)), (0, 1))
|
||||
assert str(expr) == "ArrayContraction(ArrayDiagonal(ArrayTensorProduct(M, N, P), (0, 1)), (0, 1))"
|
||||
assert expr.doit() == ArrayDiagonal(ArrayContraction(ArrayTensorProduct(N, M, P), (0, 1)), (0, 1))
|
||||
|
||||
expr = ArrayContraction(PermuteDims(M, [1, 0]), (0, 1))
|
||||
assert str(expr) == "ArrayContraction(PermuteDims(M, (0 1)), (0, 1))"
|
||||
assert expr.doit() == ArrayContraction(M, (0, 1))
|
||||
|
||||
# ArrayDiagonal:
|
||||
|
||||
expr = ArrayDiagonal(ArrayDiagonal(tp, (0, 2)), (0, 1))
|
||||
assert str(expr) == "ArrayDiagonal(ArrayDiagonal(ArrayTensorProduct(M, N, P), (0, 2)), (0, 1))"
|
||||
assert expr.doit() == ArrayDiagonal(tp, (0, 2), (1, 3))
|
||||
|
||||
expr = ArrayDiagonal(ArrayDiagonal(ArrayDiagonal(tp, (0, 1)), (0, 1)), (0, 1))
|
||||
assert expr.doit() == ArrayDiagonal(tp, (0, 1), (2, 3), (4, 5))
|
||||
assert expr._canonicalize() == expr.doit()
|
||||
|
||||
expr = ArrayDiagonal(ArrayContraction(tp, (0, 1)), (0, 1))
|
||||
assert str(expr) == "ArrayDiagonal(ArrayContraction(ArrayTensorProduct(M, N, P), (0, 1)), (0, 1))"
|
||||
assert expr.doit() == expr
|
||||
|
||||
expr = ArrayDiagonal(PermuteDims(M, [1, 0]), (0, 1))
|
||||
assert str(expr) == "ArrayDiagonal(PermuteDims(M, (0 1)), (0, 1))"
|
||||
assert expr.doit() == ArrayDiagonal(M, (0, 1))
|
||||
|
||||
# ArrayAdd:
|
||||
|
||||
expr = ArrayAdd(M)
|
||||
assert isinstance(expr, ArrayAdd)
|
||||
assert expr.doit() == M
|
||||
|
||||
expr = ArrayAdd(ArrayAdd(M, N), P)
|
||||
assert str(expr) == "ArrayAdd(ArrayAdd(M, N), P)"
|
||||
assert expr.doit() == ArrayAdd(M, N, P)
|
||||
|
||||
expr = ArrayAdd(M, ArrayAdd(N, ArrayAdd(P, M)))
|
||||
assert expr.doit() == ArrayAdd(M, N, P, M)
|
||||
assert expr._canonicalize() == ArrayAdd(M, N, ArrayAdd(P, M))
|
||||
|
||||
expr = ArrayAdd(M, ZeroArray(k, k), N)
|
||||
assert str(expr) == "ArrayAdd(M, ZeroArray(k, k), N)"
|
||||
assert expr.doit() == ArrayAdd(M, N)
|
||||
|
||||
# PermuteDims:
|
||||
|
||||
expr = PermuteDims(PermuteDims(M, [1, 0]), [1, 0])
|
||||
assert str(expr) == "PermuteDims(PermuteDims(M, (0 1)), (0 1))"
|
||||
assert expr.doit() == M
|
||||
|
||||
expr = PermuteDims(PermuteDims(PermuteDims(M, [1, 0]), [1, 0]), [1, 0])
|
||||
assert expr.doit() == PermuteDims(M, [1, 0])
|
||||
assert expr._canonicalize() == expr.doit()
|
||||
|
||||
# Reshape
|
||||
|
||||
expr = Reshape(A, (k**2,))
|
||||
assert expr.shape == (k**2,)
|
||||
assert isinstance(expr, Reshape)
|
||||
|
||||
|
||||
def test_array_expr_construction_with_functions():
|
||||
|
||||
tp = tensorproduct(M, N)
|
||||
assert tp == ArrayTensorProduct(M, N)
|
||||
|
||||
expr = tensorproduct(A, eye(2))
|
||||
assert expr == ArrayTensorProduct(A, eye(2))
|
||||
|
||||
# Contraction:
|
||||
|
||||
expr = tensorcontraction(M, (0, 1))
|
||||
assert expr == ArrayContraction(M, (0, 1))
|
||||
|
||||
expr = tensorcontraction(tp, (1, 2))
|
||||
assert expr == ArrayContraction(tp, (1, 2))
|
||||
|
||||
expr = tensorcontraction(tensorcontraction(tp, (1, 2)), (0, 1))
|
||||
assert expr == ArrayContraction(tp, (0, 3), (1, 2))
|
||||
|
||||
# Diagonalization:
|
||||
|
||||
expr = tensordiagonal(M, (0, 1))
|
||||
assert expr == ArrayDiagonal(M, (0, 1))
|
||||
|
||||
expr = tensordiagonal(tensordiagonal(tp, (0, 1)), (0, 1))
|
||||
assert expr == ArrayDiagonal(tp, (0, 1), (2, 3))
|
||||
|
||||
# Permutation of dimensions:
|
||||
|
||||
expr = permutedims(M, [1, 0])
|
||||
assert expr == PermuteDims(M, [1, 0])
|
||||
|
||||
expr = permutedims(PermuteDims(tp, [1, 0, 2, 3]), [0, 1, 3, 2])
|
||||
assert expr == PermuteDims(tp, [1, 0, 3, 2])
|
||||
|
||||
expr = PermuteDims(tp, index_order_new=["a", "b", "c", "d"], index_order_old=["d", "c", "b", "a"])
|
||||
assert expr == PermuteDims(tp, [3, 2, 1, 0])
|
||||
|
||||
arr = Array(range(32)).reshape(2, 2, 2, 2, 2)
|
||||
expr = PermuteDims(arr, index_order_new=["a", "b", "c", "d", "e"], index_order_old=['b', 'e', 'a', 'd', 'c'])
|
||||
assert expr == PermuteDims(arr, [2, 0, 4, 3, 1])
|
||||
assert expr.as_explicit() == permutedims(arr, index_order_new=["a", "b", "c", "d", "e"], index_order_old=['b', 'e', 'a', 'd', 'c'])
|
||||
|
||||
|
||||
def test_array_element_expressions():
|
||||
# Check commutative property:
|
||||
assert M[0, 0]*N[0, 0] == N[0, 0]*M[0, 0]
|
||||
|
||||
# Check derivatives:
|
||||
assert M[0, 0].diff(M[0, 0]) == 1
|
||||
assert M[0, 0].diff(M[1, 0]) == 0
|
||||
assert M[0, 0].diff(N[0, 0]) == 0
|
||||
assert M[0, 1].diff(M[i, j]) == KroneckerDelta(i, 0)*KroneckerDelta(j, 1)
|
||||
assert M[0, 1].diff(N[i, j]) == 0
|
||||
|
||||
K4 = ArraySymbol("K4", shape=(k, k, k, k))
|
||||
|
||||
assert K4[i, j, k, l].diff(K4[1, 2, 3, 4]) == (
|
||||
KroneckerDelta(i, 1)*KroneckerDelta(j, 2)*KroneckerDelta(k, 3)*KroneckerDelta(l, 4)
|
||||
)
|
||||
|
||||
|
||||
def test_array_expr_reshape():
|
||||
|
||||
A = MatrixSymbol("A", 2, 2)
|
||||
B = ArraySymbol("B", (2, 2, 2))
|
||||
C = Array([1, 2, 3, 4])
|
||||
|
||||
expr = Reshape(A, (4,))
|
||||
assert expr.expr == A
|
||||
assert expr.shape == (4,)
|
||||
assert expr.as_explicit() == Array([A[0, 0], A[0, 1], A[1, 0], A[1, 1]])
|
||||
|
||||
expr = Reshape(B, (2, 4))
|
||||
assert expr.expr == B
|
||||
assert expr.shape == (2, 4)
|
||||
ee = expr.as_explicit()
|
||||
assert isinstance(ee, ImmutableDenseNDimArray)
|
||||
assert ee.shape == (2, 4)
|
||||
assert ee == Array([[B[0, 0, 0], B[0, 0, 1], B[0, 1, 0], B[0, 1, 1]], [B[1, 0, 0], B[1, 0, 1], B[1, 1, 0], B[1, 1, 1]]])
|
||||
|
||||
expr = Reshape(A, (k, 2))
|
||||
assert expr.shape == (k, 2)
|
||||
|
||||
raises(ValueError, lambda: Reshape(A, (2, 3)))
|
||||
raises(ValueError, lambda: Reshape(A, (3,)))
|
||||
|
||||
expr = Reshape(C, (2, 2))
|
||||
assert expr.expr == C
|
||||
assert expr.shape == (2, 2)
|
||||
assert expr.doit() == Array([[1, 2], [3, 4]])
|
||||
|
||||
|
||||
def test_array_expr_as_explicit_with_explicit_component_arrays():
|
||||
# Test if .as_explicit() works with explicit-component arrays
|
||||
# nested in array expressions:
|
||||
from sympy.abc import x, y, z, t
|
||||
A = Array([[x, y], [z, t]])
|
||||
assert ArrayTensorProduct(A, A).as_explicit() == tensorproduct(A, A)
|
||||
assert ArrayDiagonal(A, (0, 1)).as_explicit() == tensordiagonal(A, (0, 1))
|
||||
assert ArrayContraction(A, (0, 1)).as_explicit() == tensorcontraction(A, (0, 1))
|
||||
assert ArrayAdd(A, A).as_explicit() == A + A
|
||||
assert ArrayElementwiseApplyFunc(sin, A).as_explicit() == A.applyfunc(sin)
|
||||
assert PermuteDims(A, [1, 0]).as_explicit() == permutedims(A, [1, 0])
|
||||
assert Reshape(A, [4]).as_explicit() == A.reshape(4)
|
||||
@@ -0,0 +1,78 @@
|
||||
from sympy.core.symbol import symbols
|
||||
from sympy.functions.elementary.trigonometric import (cos, sin)
|
||||
from sympy.matrices.expressions.matexpr import MatrixSymbol
|
||||
from sympy.matrices.expressions.special import Identity
|
||||
from sympy.matrices.expressions.applyfunc import ElementwiseApplyFunction
|
||||
from sympy.tensor.array.expressions.array_expressions import ArraySymbol, ArrayTensorProduct, \
|
||||
PermuteDims, ArrayDiagonal, ArrayElementwiseApplyFunc, ArrayContraction, _permute_dims, Reshape
|
||||
from sympy.tensor.array.expressions.arrayexpr_derivatives import array_derive
|
||||
|
||||
k = symbols("k")
|
||||
|
||||
I = Identity(k)
|
||||
X = MatrixSymbol("X", k, k)
|
||||
x = MatrixSymbol("x", k, 1)
|
||||
|
||||
A = MatrixSymbol("A", k, k)
|
||||
B = MatrixSymbol("B", k, k)
|
||||
C = MatrixSymbol("C", k, k)
|
||||
D = MatrixSymbol("D", k, k)
|
||||
|
||||
A1 = ArraySymbol("A", (3, 2, k))
|
||||
|
||||
|
||||
def test_arrayexpr_derivatives1():
|
||||
|
||||
res = array_derive(X, X)
|
||||
assert res == PermuteDims(ArrayTensorProduct(I, I), [0, 2, 1, 3])
|
||||
|
||||
cg = ArrayTensorProduct(A, X, B)
|
||||
res = array_derive(cg, X)
|
||||
assert res == _permute_dims(
|
||||
ArrayTensorProduct(I, A, I, B),
|
||||
[0, 4, 2, 3, 1, 5, 6, 7])
|
||||
|
||||
cg = ArrayContraction(X, (0, 1))
|
||||
res = array_derive(cg, X)
|
||||
assert res == ArrayContraction(ArrayTensorProduct(I, I), (1, 3))
|
||||
|
||||
cg = ArrayDiagonal(X, (0, 1))
|
||||
res = array_derive(cg, X)
|
||||
assert res == ArrayDiagonal(ArrayTensorProduct(I, I), (1, 3))
|
||||
|
||||
cg = ElementwiseApplyFunction(sin, X)
|
||||
res = array_derive(cg, X)
|
||||
assert res.dummy_eq(ArrayDiagonal(
|
||||
ArrayTensorProduct(
|
||||
ElementwiseApplyFunction(cos, X),
|
||||
I,
|
||||
I
|
||||
), (0, 3), (1, 5)))
|
||||
|
||||
cg = ArrayElementwiseApplyFunc(sin, X)
|
||||
res = array_derive(cg, X)
|
||||
assert res.dummy_eq(ArrayDiagonal(
|
||||
ArrayTensorProduct(
|
||||
I,
|
||||
I,
|
||||
ArrayElementwiseApplyFunc(cos, X)
|
||||
), (1, 4), (3, 5)))
|
||||
|
||||
res = array_derive(A1, A1)
|
||||
assert res == PermuteDims(
|
||||
ArrayTensorProduct(Identity(3), Identity(2), Identity(k)),
|
||||
[0, 2, 4, 1, 3, 5]
|
||||
)
|
||||
|
||||
cg = ArrayElementwiseApplyFunc(sin, A1)
|
||||
res = array_derive(cg, A1)
|
||||
assert res.dummy_eq(ArrayDiagonal(
|
||||
ArrayTensorProduct(
|
||||
Identity(3), Identity(2), Identity(k),
|
||||
ArrayElementwiseApplyFunc(cos, A1)
|
||||
), (1, 6), (3, 7), (5, 8)
|
||||
))
|
||||
|
||||
cg = Reshape(A, (k**2,))
|
||||
res = array_derive(cg, A)
|
||||
assert res == Reshape(PermuteDims(ArrayTensorProduct(I, I), [0, 2, 1, 3]), (k, k, k**2))
|
||||
@@ -0,0 +1,63 @@
|
||||
from sympy.core.symbol import Symbol
|
||||
from sympy.matrices.expressions.matexpr import MatrixSymbol
|
||||
from sympy.tensor.array.arrayop import (permutedims, tensorcontraction, tensordiagonal, tensorproduct)
|
||||
from sympy.tensor.array.dense_ndim_array import ImmutableDenseNDimArray
|
||||
from sympy.tensor.array.expressions.array_expressions import ZeroArray, OneArray, ArraySymbol, \
|
||||
ArrayTensorProduct, PermuteDims, ArrayDiagonal, ArrayContraction, ArrayAdd
|
||||
from sympy.testing.pytest import raises
|
||||
|
||||
|
||||
def test_array_as_explicit_call():
|
||||
|
||||
assert ZeroArray(3, 2, 4).as_explicit() == ImmutableDenseNDimArray.zeros(3, 2, 4)
|
||||
assert OneArray(3, 2, 4).as_explicit() == ImmutableDenseNDimArray([1 for i in range(3*2*4)]).reshape(3, 2, 4)
|
||||
|
||||
k = Symbol("k")
|
||||
X = ArraySymbol("X", (k, 3, 2))
|
||||
raises(ValueError, lambda: X.as_explicit())
|
||||
raises(ValueError, lambda: ZeroArray(k, 2, 3).as_explicit())
|
||||
raises(ValueError, lambda: OneArray(2, k, 2).as_explicit())
|
||||
|
||||
A = ArraySymbol("A", (3, 3))
|
||||
B = ArraySymbol("B", (3, 3))
|
||||
|
||||
texpr = tensorproduct(A, B)
|
||||
assert isinstance(texpr, ArrayTensorProduct)
|
||||
assert texpr.as_explicit() == tensorproduct(A.as_explicit(), B.as_explicit())
|
||||
|
||||
texpr = tensorcontraction(A, (0, 1))
|
||||
assert isinstance(texpr, ArrayContraction)
|
||||
assert texpr.as_explicit() == A[0, 0] + A[1, 1] + A[2, 2]
|
||||
|
||||
texpr = tensordiagonal(A, (0, 1))
|
||||
assert isinstance(texpr, ArrayDiagonal)
|
||||
assert texpr.as_explicit() == ImmutableDenseNDimArray([A[0, 0], A[1, 1], A[2, 2]])
|
||||
|
||||
texpr = permutedims(A, [1, 0])
|
||||
assert isinstance(texpr, PermuteDims)
|
||||
assert texpr.as_explicit() == permutedims(A.as_explicit(), [1, 0])
|
||||
|
||||
|
||||
def test_array_as_explicit_matrix_symbol():
|
||||
|
||||
A = MatrixSymbol("A", 3, 3)
|
||||
B = MatrixSymbol("B", 3, 3)
|
||||
|
||||
texpr = tensorproduct(A, B)
|
||||
assert isinstance(texpr, ArrayTensorProduct)
|
||||
assert texpr.as_explicit() == tensorproduct(A.as_explicit(), B.as_explicit())
|
||||
|
||||
texpr = tensorcontraction(A, (0, 1))
|
||||
assert isinstance(texpr, ArrayContraction)
|
||||
assert texpr.as_explicit() == A[0, 0] + A[1, 1] + A[2, 2]
|
||||
|
||||
texpr = tensordiagonal(A, (0, 1))
|
||||
assert isinstance(texpr, ArrayDiagonal)
|
||||
assert texpr.as_explicit() == ImmutableDenseNDimArray([A[0, 0], A[1, 1], A[2, 2]])
|
||||
|
||||
texpr = permutedims(A, [1, 0])
|
||||
assert isinstance(texpr, PermuteDims)
|
||||
assert texpr.as_explicit() == permutedims(A.as_explicit(), [1, 0])
|
||||
|
||||
expr = ArrayAdd(ArrayTensorProduct(A, B), ArrayTensorProduct(B, A))
|
||||
assert expr.as_explicit() == expr.args[0].as_explicit() + expr.args[1].as_explicit()
|
||||
@@ -0,0 +1,61 @@
|
||||
from sympy import Sum, Dummy, sin
|
||||
from sympy.tensor.array.expressions import ArraySymbol, ArrayTensorProduct, ArrayContraction, PermuteDims, \
|
||||
ArrayDiagonal, ArrayAdd, OneArray, ZeroArray, convert_indexed_to_array, ArrayElementwiseApplyFunc, Reshape
|
||||
from sympy.tensor.array.expressions.from_array_to_indexed import convert_array_to_indexed
|
||||
|
||||
from sympy.abc import i, j, k, l, m, n, o
|
||||
|
||||
|
||||
def test_convert_array_to_indexed_main():
|
||||
A = ArraySymbol("A", (3, 3, 3))
|
||||
B = ArraySymbol("B", (3, 3))
|
||||
C = ArraySymbol("C", (3, 3))
|
||||
|
||||
d_ = Dummy("d_")
|
||||
|
||||
assert convert_array_to_indexed(A, [i, j, k]) == A[i, j, k]
|
||||
|
||||
expr = ArrayTensorProduct(A, B, C)
|
||||
conv = convert_array_to_indexed(expr, [i,j,k,l,m,n,o])
|
||||
assert conv == A[i,j,k]*B[l,m]*C[n,o]
|
||||
assert convert_indexed_to_array(conv, [i,j,k,l,m,n,o]) == expr
|
||||
|
||||
expr = ArrayContraction(A, (0, 2))
|
||||
assert convert_array_to_indexed(expr, [i]).dummy_eq(Sum(A[d_, i, d_], (d_, 0, 2)))
|
||||
|
||||
expr = ArrayDiagonal(A, (0, 2))
|
||||
assert convert_array_to_indexed(expr, [i, j]) == A[j, i, j]
|
||||
|
||||
expr = PermuteDims(A, [1, 2, 0])
|
||||
conv = convert_array_to_indexed(expr, [i, j, k])
|
||||
assert conv == A[k, i, j]
|
||||
assert convert_indexed_to_array(conv, [i, j, k]) == expr
|
||||
|
||||
expr = ArrayAdd(B, C, PermuteDims(C, [1, 0]))
|
||||
conv = convert_array_to_indexed(expr, [i, j])
|
||||
assert conv == B[i, j] + C[i, j] + C[j, i]
|
||||
assert convert_indexed_to_array(conv, [i, j]) == expr
|
||||
|
||||
expr = ArrayElementwiseApplyFunc(sin, A)
|
||||
conv = convert_array_to_indexed(expr, [i, j, k])
|
||||
assert conv == sin(A[i, j, k])
|
||||
assert convert_indexed_to_array(conv, [i, j, k]).dummy_eq(expr)
|
||||
|
||||
assert convert_array_to_indexed(OneArray(3, 3), [i, j]) == 1
|
||||
assert convert_array_to_indexed(ZeroArray(3, 3), [i, j]) == 0
|
||||
|
||||
expr = Reshape(A, (27,))
|
||||
assert convert_array_to_indexed(expr, [i]) == A[i // 9, i // 3 % 3, i % 3]
|
||||
|
||||
X = ArraySymbol("X", (2, 3, 4, 5, 6))
|
||||
expr = Reshape(X, (2*3*4*5*6,))
|
||||
assert convert_array_to_indexed(expr, [i]) == X[i // 360, i // 120 % 3, i // 30 % 4, i // 6 % 5, i % 6]
|
||||
|
||||
expr = Reshape(X, (4, 9, 2, 2, 5))
|
||||
one_index = 180*i + 20*j + 10*k + 5*l + m
|
||||
expected = X[one_index // (3*4*5*6), one_index // (4*5*6) % 3, one_index // (5*6) % 4, one_index // 6 % 5, one_index % 6]
|
||||
assert convert_array_to_indexed(expr, [i, j, k, l, m]) == expected
|
||||
|
||||
X = ArraySymbol("X", (2*3*5,))
|
||||
expr = Reshape(X, (2, 3, 5))
|
||||
assert convert_array_to_indexed(expr, [i, j, k]) == X[15*i + 5*j + k]
|
||||
@@ -0,0 +1,689 @@
|
||||
from sympy import Lambda, S, Dummy, KroneckerProduct
|
||||
from sympy.core.symbol import symbols
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
from sympy.functions.elementary.trigonometric import cos, sin
|
||||
from sympy.matrices.expressions.hadamard import HadamardProduct, HadamardPower
|
||||
from sympy.matrices.expressions.special import (Identity, OneMatrix, ZeroMatrix)
|
||||
from sympy.matrices.expressions.matexpr import MatrixElement
|
||||
from sympy.tensor.array.expressions.from_matrix_to_array import convert_matrix_to_array
|
||||
from sympy.tensor.array.expressions.from_array_to_matrix import _support_function_tp1_recognize, \
|
||||
_array_diag2contr_diagmatrix, convert_array_to_matrix, _remove_trivial_dims, _array2matrix, \
|
||||
_combine_removed, identify_removable_identity_matrices, _array_contraction_to_diagonal_multiple_identity
|
||||
from sympy.matrices.expressions.matexpr import MatrixSymbol
|
||||
from sympy.combinatorics import Permutation
|
||||
from sympy.matrices.expressions.diagonal import DiagMatrix, DiagonalMatrix
|
||||
from sympy.matrices import Trace, MatMul, Transpose
|
||||
from sympy.tensor.array.expressions.array_expressions import ZeroArray, OneArray, \
|
||||
ArrayElement, ArraySymbol, ArrayElementwiseApplyFunc, _array_tensor_product, _array_contraction, \
|
||||
_array_diagonal, _permute_dims, PermuteDims, ArrayAdd, ArrayDiagonal, ArrayContraction, ArrayTensorProduct
|
||||
from sympy.testing.pytest import raises
|
||||
|
||||
|
||||
i, j, k, l, m, n = symbols("i j k l m n")
|
||||
|
||||
I = Identity(k)
|
||||
I1 = Identity(1)
|
||||
|
||||
M = MatrixSymbol("M", k, k)
|
||||
N = MatrixSymbol("N", k, k)
|
||||
P = MatrixSymbol("P", k, k)
|
||||
Q = MatrixSymbol("Q", k, k)
|
||||
|
||||
A = MatrixSymbol("A", k, k)
|
||||
B = MatrixSymbol("B", k, k)
|
||||
C = MatrixSymbol("C", k, k)
|
||||
D = MatrixSymbol("D", k, k)
|
||||
|
||||
X = MatrixSymbol("X", k, k)
|
||||
Y = MatrixSymbol("Y", k, k)
|
||||
|
||||
a = MatrixSymbol("a", k, 1)
|
||||
b = MatrixSymbol("b", k, 1)
|
||||
c = MatrixSymbol("c", k, 1)
|
||||
d = MatrixSymbol("d", k, 1)
|
||||
|
||||
x = MatrixSymbol("x", k, 1)
|
||||
y = MatrixSymbol("y", k, 1)
|
||||
|
||||
|
||||
def test_arrayexpr_convert_array_to_matrix():
|
||||
|
||||
cg = _array_contraction(_array_tensor_product(M), (0, 1))
|
||||
assert convert_array_to_matrix(cg) == Trace(M)
|
||||
|
||||
cg = _array_contraction(_array_tensor_product(M, N), (0, 1), (2, 3))
|
||||
assert convert_array_to_matrix(cg) == Trace(M) * Trace(N)
|
||||
|
||||
cg = _array_contraction(_array_tensor_product(M, N), (0, 3), (1, 2))
|
||||
assert convert_array_to_matrix(cg) == Trace(M * N)
|
||||
|
||||
cg = _array_contraction(_array_tensor_product(M, N), (0, 2), (1, 3))
|
||||
assert convert_array_to_matrix(cg) == Trace(M * N.T)
|
||||
|
||||
cg = convert_matrix_to_array(M * N * P)
|
||||
assert convert_array_to_matrix(cg) == M * N * P
|
||||
|
||||
cg = convert_matrix_to_array(M * N.T * P)
|
||||
assert convert_array_to_matrix(cg) == M * N.T * P
|
||||
|
||||
cg = _array_contraction(_array_tensor_product(M,N,P,Q), (1, 2), (5, 6))
|
||||
assert convert_array_to_matrix(cg) == _array_tensor_product(M * N, P * Q)
|
||||
|
||||
cg = _array_contraction(_array_tensor_product(-2, M, N), (1, 2))
|
||||
assert convert_array_to_matrix(cg) == -2 * M * N
|
||||
|
||||
a = MatrixSymbol("a", k, 1)
|
||||
b = MatrixSymbol("b", k, 1)
|
||||
c = MatrixSymbol("c", k, 1)
|
||||
cg = PermuteDims(
|
||||
_array_contraction(
|
||||
_array_tensor_product(
|
||||
a,
|
||||
ArrayAdd(
|
||||
_array_tensor_product(b, c),
|
||||
_array_tensor_product(c, b),
|
||||
)
|
||||
), (2, 4)), [0, 1, 3, 2])
|
||||
assert convert_array_to_matrix(cg) == a * (b.T * c + c.T * b)
|
||||
|
||||
za = ZeroArray(m, n)
|
||||
assert convert_array_to_matrix(za) == ZeroMatrix(m, n)
|
||||
|
||||
cg = _array_tensor_product(3, M)
|
||||
assert convert_array_to_matrix(cg) == 3 * M
|
||||
|
||||
# Partial conversion to matrix multiplication:
|
||||
expr = _array_contraction(_array_tensor_product(M, N, P, Q), (0, 2), (1, 4, 6))
|
||||
assert convert_array_to_matrix(expr) == _array_contraction(_array_tensor_product(M.T*N, P, Q), (0, 2, 4))
|
||||
|
||||
x = MatrixSymbol("x", k, 1)
|
||||
cg = PermuteDims(
|
||||
_array_contraction(_array_tensor_product(OneArray(1), x, OneArray(1), DiagMatrix(Identity(1))),
|
||||
(0, 5)), Permutation(1, 2, 3))
|
||||
assert convert_array_to_matrix(cg) == x
|
||||
|
||||
expr = ArrayAdd(M, PermuteDims(M, [1, 0]))
|
||||
assert convert_array_to_matrix(expr) == M + Transpose(M)
|
||||
|
||||
|
||||
def test_arrayexpr_convert_array_to_matrix2():
|
||||
cg = _array_contraction(_array_tensor_product(M, N), (1, 3))
|
||||
assert convert_array_to_matrix(cg) == M * N.T
|
||||
|
||||
cg = PermuteDims(_array_tensor_product(M, N), Permutation([0, 1, 3, 2]))
|
||||
assert convert_array_to_matrix(cg) == _array_tensor_product(M, N.T)
|
||||
|
||||
cg = _array_tensor_product(M, PermuteDims(N, Permutation([1, 0])))
|
||||
assert convert_array_to_matrix(cg) == _array_tensor_product(M, N.T)
|
||||
|
||||
cg = _array_contraction(
|
||||
PermuteDims(
|
||||
_array_tensor_product(M, N, P, Q), Permutation([0, 2, 3, 1, 4, 5, 7, 6])),
|
||||
(1, 2), (3, 5)
|
||||
)
|
||||
assert convert_array_to_matrix(cg) == _array_tensor_product(M * P.T * Trace(N), Q.T)
|
||||
|
||||
cg = _array_contraction(
|
||||
_array_tensor_product(M, N, P, PermuteDims(Q, Permutation([1, 0]))),
|
||||
(1, 5), (2, 3)
|
||||
)
|
||||
assert convert_array_to_matrix(cg) == _array_tensor_product(M * P.T * Trace(N), Q.T)
|
||||
|
||||
cg = _array_tensor_product(M, PermuteDims(N, [1, 0]))
|
||||
assert convert_array_to_matrix(cg) == _array_tensor_product(M, N.T)
|
||||
|
||||
cg = _array_tensor_product(PermuteDims(M, [1, 0]), PermuteDims(N, [1, 0]))
|
||||
assert convert_array_to_matrix(cg) == _array_tensor_product(M.T, N.T)
|
||||
|
||||
cg = _array_tensor_product(PermuteDims(N, [1, 0]), PermuteDims(M, [1, 0]))
|
||||
assert convert_array_to_matrix(cg) == _array_tensor_product(N.T, M.T)
|
||||
|
||||
cg = _array_contraction(M, (0,), (1,))
|
||||
assert convert_array_to_matrix(cg) == OneMatrix(1, k)*M*OneMatrix(k, 1)
|
||||
|
||||
cg = _array_contraction(x, (0,), (1,))
|
||||
assert convert_array_to_matrix(cg) == OneMatrix(1, k)*x
|
||||
|
||||
Xm = MatrixSymbol("Xm", m, n)
|
||||
cg = _array_contraction(Xm, (0,), (1,))
|
||||
assert convert_array_to_matrix(cg) == OneMatrix(1, m)*Xm*OneMatrix(n, 1)
|
||||
|
||||
|
||||
def test_arrayexpr_convert_array_to_diagonalized_vector():
|
||||
|
||||
# Check matrix recognition over trivial dimensions:
|
||||
|
||||
cg = _array_tensor_product(a, b)
|
||||
assert convert_array_to_matrix(cg) == a * b.T
|
||||
|
||||
cg = _array_tensor_product(I1, a, b)
|
||||
assert convert_array_to_matrix(cg) == a * b.T
|
||||
|
||||
# Recognize trace inside a tensor product:
|
||||
|
||||
cg = _array_contraction(_array_tensor_product(A, B, C), (0, 3), (1, 2))
|
||||
assert convert_array_to_matrix(cg) == Trace(A * B) * C
|
||||
|
||||
# Transform diagonal operator to contraction:
|
||||
|
||||
cg = _array_diagonal(_array_tensor_product(A, a), (1, 2))
|
||||
assert _array_diag2contr_diagmatrix(cg) == _array_contraction(_array_tensor_product(A, OneArray(1), DiagMatrix(a)), (1, 3))
|
||||
assert convert_array_to_matrix(cg) == A * DiagMatrix(a)
|
||||
|
||||
cg = _array_diagonal(_array_tensor_product(a, b), (0, 2))
|
||||
assert _array_diag2contr_diagmatrix(cg) == _permute_dims(
|
||||
_array_contraction(_array_tensor_product(DiagMatrix(a), OneArray(1), b), (0, 3)), [1, 2, 0]
|
||||
)
|
||||
assert convert_array_to_matrix(cg) == b.T * DiagMatrix(a)
|
||||
|
||||
cg = _array_diagonal(_array_tensor_product(A, a), (0, 2))
|
||||
assert _array_diag2contr_diagmatrix(cg) == _array_contraction(_array_tensor_product(A, OneArray(1), DiagMatrix(a)), (0, 3))
|
||||
assert convert_array_to_matrix(cg) == A.T * DiagMatrix(a)
|
||||
|
||||
cg = _array_diagonal(_array_tensor_product(I, x, I1), (0, 2), (3, 5))
|
||||
assert _array_diag2contr_diagmatrix(cg) == _array_contraction(_array_tensor_product(I, OneArray(1), I1, DiagMatrix(x)), (0, 5))
|
||||
assert convert_array_to_matrix(cg) == DiagMatrix(x)
|
||||
|
||||
cg = _array_diagonal(_array_tensor_product(I, x, A, B), (1, 2), (5, 6))
|
||||
assert _array_diag2contr_diagmatrix(cg) == _array_diagonal(_array_contraction(_array_tensor_product(I, OneArray(1), A, B, DiagMatrix(x)), (1, 7)), (5, 6))
|
||||
# TODO: this is returning a wrong result:
|
||||
# convert_array_to_matrix(cg)
|
||||
|
||||
cg = _array_diagonal(_array_tensor_product(I1, a, b), (1, 3, 5))
|
||||
assert convert_array_to_matrix(cg) == a*b.T
|
||||
|
||||
cg = _array_diagonal(_array_tensor_product(I1, a, b), (1, 3))
|
||||
assert _array_diag2contr_diagmatrix(cg) == _array_contraction(_array_tensor_product(OneArray(1), a, b, I1), (2, 6))
|
||||
assert convert_array_to_matrix(cg) == a*b.T
|
||||
|
||||
cg = _array_diagonal(_array_tensor_product(x, I1), (1, 2))
|
||||
assert isinstance(cg, ArrayDiagonal)
|
||||
assert cg.diagonal_indices == ((1, 2),)
|
||||
assert convert_array_to_matrix(cg) == x
|
||||
|
||||
cg = _array_diagonal(_array_tensor_product(x, I), (0, 2))
|
||||
assert _array_diag2contr_diagmatrix(cg) == _array_contraction(_array_tensor_product(OneArray(1), I, DiagMatrix(x)), (1, 3))
|
||||
assert convert_array_to_matrix(cg).doit() == DiagMatrix(x)
|
||||
|
||||
raises(ValueError, lambda: _array_diagonal(x, (1,)))
|
||||
|
||||
# Ignore identity matrices with contractions:
|
||||
|
||||
cg = _array_contraction(_array_tensor_product(I, A, I, I), (0, 2), (1, 3), (5, 7))
|
||||
assert cg.split_multiple_contractions() == cg
|
||||
assert convert_array_to_matrix(cg) == Trace(A) * I
|
||||
|
||||
cg = _array_contraction(_array_tensor_product(Trace(A) * I, I, I), (1, 5), (3, 4))
|
||||
assert cg.split_multiple_contractions() == cg
|
||||
assert convert_array_to_matrix(cg).doit() == Trace(A) * I
|
||||
|
||||
# Add DiagMatrix when required:
|
||||
|
||||
cg = _array_contraction(_array_tensor_product(A, a), (1, 2))
|
||||
assert cg.split_multiple_contractions() == cg
|
||||
assert convert_array_to_matrix(cg) == A * a
|
||||
|
||||
cg = _array_contraction(_array_tensor_product(A, a, B), (1, 2, 4))
|
||||
assert cg.split_multiple_contractions() == _array_contraction(_array_tensor_product(A, DiagMatrix(a), OneArray(1), B), (1, 2), (3, 5))
|
||||
assert convert_array_to_matrix(cg) == A * DiagMatrix(a) * B
|
||||
|
||||
cg = _array_contraction(_array_tensor_product(A, a, B), (0, 2, 4))
|
||||
assert cg.split_multiple_contractions() == _array_contraction(_array_tensor_product(A, DiagMatrix(a), OneArray(1), B), (0, 2), (3, 5))
|
||||
assert convert_array_to_matrix(cg) == A.T * DiagMatrix(a) * B
|
||||
|
||||
cg = _array_contraction(_array_tensor_product(A, a, b, a.T, B), (0, 2, 4, 7, 9))
|
||||
assert cg.split_multiple_contractions() == _array_contraction(_array_tensor_product(A, DiagMatrix(a), OneArray(1),
|
||||
DiagMatrix(b), OneArray(1), DiagMatrix(a), OneArray(1), B),
|
||||
(0, 2), (3, 5), (6, 9), (8, 12))
|
||||
assert convert_array_to_matrix(cg) == A.T * DiagMatrix(a) * DiagMatrix(b) * DiagMatrix(a) * B.T
|
||||
|
||||
cg = _array_contraction(_array_tensor_product(I1, I1, I1), (1, 2, 4))
|
||||
assert cg.split_multiple_contractions() == _array_contraction(_array_tensor_product(I1, I1, OneArray(1), I1), (1, 2), (3, 5))
|
||||
assert convert_array_to_matrix(cg) == 1
|
||||
|
||||
cg = _array_contraction(_array_tensor_product(I, I, I, I, A), (1, 2, 8), (5, 6, 9))
|
||||
assert convert_array_to_matrix(cg.split_multiple_contractions()).doit() == A
|
||||
|
||||
cg = _array_contraction(_array_tensor_product(A, a, C, a, B), (1, 2, 4), (5, 6, 8))
|
||||
expected = _array_contraction(_array_tensor_product(A, DiagMatrix(a), OneArray(1), C, DiagMatrix(a), OneArray(1), B), (1, 3), (2, 5), (6, 7), (8, 10))
|
||||
assert cg.split_multiple_contractions() == expected
|
||||
assert convert_array_to_matrix(cg) == A * DiagMatrix(a) * C * DiagMatrix(a) * B
|
||||
|
||||
cg = _array_contraction(_array_tensor_product(a, I1, b, I1, (a.T*b).applyfunc(cos)), (1, 2, 8), (5, 6, 9))
|
||||
expected = _array_contraction(_array_tensor_product(a, I1, OneArray(1), b, I1, OneArray(1), (a.T*b).applyfunc(cos)),
|
||||
(1, 3), (2, 10), (6, 8), (7, 11))
|
||||
assert cg.split_multiple_contractions().dummy_eq(expected)
|
||||
assert convert_array_to_matrix(cg).doit().dummy_eq(MatMul(a, (a.T * b).applyfunc(cos), b.T))
|
||||
|
||||
|
||||
def test_arrayexpr_convert_array_contraction_tp_additions():
|
||||
a = ArrayAdd(
|
||||
_array_tensor_product(M, N),
|
||||
_array_tensor_product(N, M)
|
||||
)
|
||||
tp = _array_tensor_product(P, a, Q)
|
||||
expr = _array_contraction(tp, (3, 4))
|
||||
expected = _array_tensor_product(
|
||||
P,
|
||||
ArrayAdd(
|
||||
_array_contraction(_array_tensor_product(M, N), (1, 2)),
|
||||
_array_contraction(_array_tensor_product(N, M), (1, 2)),
|
||||
),
|
||||
Q
|
||||
)
|
||||
assert expr == expected
|
||||
assert convert_array_to_matrix(expr) == _array_tensor_product(P, M * N + N * M, Q)
|
||||
|
||||
expr = _array_contraction(tp, (1, 2), (3, 4), (5, 6))
|
||||
result = _array_contraction(
|
||||
_array_tensor_product(
|
||||
P,
|
||||
ArrayAdd(
|
||||
_array_contraction(_array_tensor_product(M, N), (1, 2)),
|
||||
_array_contraction(_array_tensor_product(N, M), (1, 2)),
|
||||
),
|
||||
Q
|
||||
), (1, 2), (3, 4))
|
||||
assert expr == result
|
||||
assert convert_array_to_matrix(expr) == P * (M * N + N * M) * Q
|
||||
|
||||
|
||||
def test_arrayexpr_convert_array_to_implicit_matmul():
|
||||
# Trivial dimensions are suppressed, so the result can be expressed in matrix form:
|
||||
|
||||
cg = _array_tensor_product(a, b)
|
||||
assert convert_array_to_matrix(cg) == a * b.T
|
||||
|
||||
cg = _array_tensor_product(a, b, I)
|
||||
assert convert_array_to_matrix(cg) == _array_tensor_product(a*b.T, I)
|
||||
|
||||
cg = _array_tensor_product(I, a, b)
|
||||
assert convert_array_to_matrix(cg) == _array_tensor_product(I, a*b.T)
|
||||
|
||||
cg = _array_tensor_product(a, I, b)
|
||||
assert convert_array_to_matrix(cg) == _array_tensor_product(a, I, b)
|
||||
|
||||
cg = _array_contraction(_array_tensor_product(I, I), (1, 2))
|
||||
assert convert_array_to_matrix(cg) == I
|
||||
|
||||
cg = PermuteDims(_array_tensor_product(I, Identity(1)), [0, 2, 1, 3])
|
||||
assert convert_array_to_matrix(cg) == I
|
||||
|
||||
|
||||
def test_arrayexpr_convert_array_to_matrix_remove_trivial_dims():
|
||||
|
||||
# Tensor Product:
|
||||
assert _remove_trivial_dims(_array_tensor_product(a, b)) == (a * b.T, [1, 3])
|
||||
assert _remove_trivial_dims(_array_tensor_product(a.T, b)) == (a * b.T, [0, 3])
|
||||
assert _remove_trivial_dims(_array_tensor_product(a, b.T)) == (a * b.T, [1, 2])
|
||||
assert _remove_trivial_dims(_array_tensor_product(a.T, b.T)) == (a * b.T, [0, 2])
|
||||
|
||||
assert _remove_trivial_dims(_array_tensor_product(I, a.T, b.T)) == (_array_tensor_product(I, a * b.T), [2, 4])
|
||||
assert _remove_trivial_dims(_array_tensor_product(a.T, I, b.T)) == (_array_tensor_product(a.T, I, b.T), [])
|
||||
|
||||
assert _remove_trivial_dims(_array_tensor_product(a, I)) == (_array_tensor_product(a, I), [])
|
||||
assert _remove_trivial_dims(_array_tensor_product(I, a)) == (_array_tensor_product(I, a), [])
|
||||
|
||||
assert _remove_trivial_dims(_array_tensor_product(a.T, b.T, c, d)) == (
|
||||
_array_tensor_product(a * b.T, c * d.T), [0, 2, 5, 7])
|
||||
assert _remove_trivial_dims(_array_tensor_product(a.T, I, b.T, c, d, I)) == (
|
||||
_array_tensor_product(a.T, I, b*c.T, d, I), [4, 7])
|
||||
|
||||
# Addition:
|
||||
|
||||
cg = ArrayAdd(_array_tensor_product(a, b), _array_tensor_product(c, d))
|
||||
assert _remove_trivial_dims(cg) == (a * b.T + c * d.T, [1, 3])
|
||||
|
||||
# Permute Dims:
|
||||
|
||||
cg = PermuteDims(_array_tensor_product(a, b), Permutation(3)(1, 2))
|
||||
assert _remove_trivial_dims(cg) == (a * b.T, [2, 3])
|
||||
|
||||
cg = PermuteDims(_array_tensor_product(a, I, b), Permutation(5)(1, 2, 3, 4))
|
||||
assert _remove_trivial_dims(cg) == (cg, [])
|
||||
|
||||
cg = PermuteDims(_array_tensor_product(I, b, a), Permutation(5)(1, 2, 4, 5, 3))
|
||||
assert _remove_trivial_dims(cg) == (PermuteDims(_array_tensor_product(I, b * a.T), [0, 2, 3, 1]), [4, 5])
|
||||
|
||||
# Diagonal:
|
||||
|
||||
cg = _array_diagonal(_array_tensor_product(M, a), (1, 2))
|
||||
assert _remove_trivial_dims(cg) == (cg, [])
|
||||
|
||||
# Contraction:
|
||||
|
||||
cg = _array_contraction(_array_tensor_product(M, a), (1, 2))
|
||||
assert _remove_trivial_dims(cg) == (cg, [])
|
||||
|
||||
# A few more cases to test the removal and shift of nested removed axes
|
||||
# with array contractions and array diagonals:
|
||||
tp = _array_tensor_product(
|
||||
OneMatrix(1, 1),
|
||||
M,
|
||||
x,
|
||||
OneMatrix(1, 1),
|
||||
Identity(1),
|
||||
)
|
||||
|
||||
expr = _array_contraction(tp, (1, 8))
|
||||
rexpr, removed = _remove_trivial_dims(expr)
|
||||
assert removed == [0, 5, 6, 7]
|
||||
|
||||
expr = _array_contraction(tp, (1, 8), (3, 4))
|
||||
rexpr, removed = _remove_trivial_dims(expr)
|
||||
assert removed == [0, 3, 4, 5]
|
||||
|
||||
expr = _array_diagonal(tp, (1, 8))
|
||||
rexpr, removed = _remove_trivial_dims(expr)
|
||||
assert removed == [0, 5, 6, 7, 8]
|
||||
|
||||
expr = _array_diagonal(tp, (1, 8), (3, 4))
|
||||
rexpr, removed = _remove_trivial_dims(expr)
|
||||
assert removed == [0, 3, 4, 5, 6]
|
||||
|
||||
expr = _array_diagonal(_array_contraction(_array_tensor_product(A, x, I, I1), (1, 2, 5)), (1, 4))
|
||||
rexpr, removed = _remove_trivial_dims(expr)
|
||||
assert removed == [2, 3]
|
||||
|
||||
cg = _array_diagonal(_array_tensor_product(PermuteDims(_array_tensor_product(x, I1), Permutation(1, 2, 3)), (x.T*x).applyfunc(sqrt)), (2, 4), (3, 5))
|
||||
rexpr, removed = _remove_trivial_dims(cg)
|
||||
assert removed == [1, 2]
|
||||
|
||||
# Contractions with identity matrices need to be followed by a permutation
|
||||
# in order
|
||||
cg = _array_contraction(_array_tensor_product(A, B, C, M, I), (1, 8))
|
||||
ret, removed = _remove_trivial_dims(cg)
|
||||
assert ret == PermuteDims(_array_tensor_product(A, B, C, M), [0, 2, 3, 4, 5, 6, 7, 1])
|
||||
assert removed == []
|
||||
|
||||
cg = _array_contraction(_array_tensor_product(A, B, C, M, I), (1, 8), (3, 4))
|
||||
ret, removed = _remove_trivial_dims(cg)
|
||||
assert ret == PermuteDims(_array_contraction(_array_tensor_product(A, B, C, M), (3, 4)), [0, 2, 3, 4, 5, 1])
|
||||
assert removed == []
|
||||
|
||||
# Trivial matrices are sometimes inserted into MatMul expressions:
|
||||
|
||||
cg = _array_tensor_product(b*b.T, a.T*a)
|
||||
ret, removed = _remove_trivial_dims(cg)
|
||||
assert ret == b*a.T*a*b.T
|
||||
assert removed == [2, 3]
|
||||
|
||||
Xs = ArraySymbol("X", (3, 2, k))
|
||||
cg = _array_tensor_product(M, Xs, b.T*c, a*a.T, b*b.T, c.T*d)
|
||||
ret, removed = _remove_trivial_dims(cg)
|
||||
assert ret == _array_tensor_product(M, Xs, a*b.T*c*c.T*d*a.T, b*b.T)
|
||||
assert removed == [5, 6, 11, 12]
|
||||
|
||||
cg = _array_diagonal(_array_tensor_product(I, I1, x), (1, 4), (3, 5))
|
||||
assert _remove_trivial_dims(cg) == (PermuteDims(_array_diagonal(_array_tensor_product(I, x), (1, 2)), Permutation(1, 2)), [1])
|
||||
|
||||
expr = _array_diagonal(_array_tensor_product(x, I, y), (0, 2))
|
||||
assert _remove_trivial_dims(expr) == (PermuteDims(_array_tensor_product(DiagMatrix(x), y), [1, 2, 3, 0]), [0])
|
||||
|
||||
expr = _array_diagonal(_array_tensor_product(x, I, y), (0, 2), (3, 4))
|
||||
assert _remove_trivial_dims(expr) == (expr, [])
|
||||
|
||||
|
||||
def test_arrayexpr_convert_array_to_matrix_diag2contraction_diagmatrix():
|
||||
cg = _array_diagonal(_array_tensor_product(M, a), (1, 2))
|
||||
res = _array_diag2contr_diagmatrix(cg)
|
||||
assert res.shape == cg.shape
|
||||
assert res == _array_contraction(_array_tensor_product(M, OneArray(1), DiagMatrix(a)), (1, 3))
|
||||
|
||||
raises(ValueError, lambda: _array_diagonal(_array_tensor_product(a, M), (1, 2)))
|
||||
|
||||
cg = _array_diagonal(_array_tensor_product(a.T, M), (1, 2))
|
||||
res = _array_diag2contr_diagmatrix(cg)
|
||||
assert res.shape == cg.shape
|
||||
assert res == _array_contraction(_array_tensor_product(OneArray(1), M, DiagMatrix(a.T)), (1, 4))
|
||||
|
||||
cg = _array_diagonal(_array_tensor_product(a.T, M, N, b.T), (1, 2), (4, 7))
|
||||
res = _array_diag2contr_diagmatrix(cg)
|
||||
assert res.shape == cg.shape
|
||||
assert res == _array_contraction(
|
||||
_array_tensor_product(OneArray(1), M, N, OneArray(1), DiagMatrix(a.T), DiagMatrix(b.T)), (1, 7), (3, 9))
|
||||
|
||||
cg = _array_diagonal(_array_tensor_product(a, M, N, b.T), (0, 2), (4, 7))
|
||||
res = _array_diag2contr_diagmatrix(cg)
|
||||
assert res.shape == cg.shape
|
||||
assert res == _array_contraction(
|
||||
_array_tensor_product(OneArray(1), M, N, OneArray(1), DiagMatrix(a), DiagMatrix(b.T)), (1, 6), (3, 9))
|
||||
|
||||
cg = _array_diagonal(_array_tensor_product(a, M, N, b.T), (0, 4), (3, 7))
|
||||
res = _array_diag2contr_diagmatrix(cg)
|
||||
assert res.shape == cg.shape
|
||||
assert res == _array_contraction(
|
||||
_array_tensor_product(OneArray(1), M, N, OneArray(1), DiagMatrix(a), DiagMatrix(b.T)), (3, 6), (2, 9))
|
||||
|
||||
I1 = Identity(1)
|
||||
x = MatrixSymbol("x", k, 1)
|
||||
A = MatrixSymbol("A", k, k)
|
||||
cg = _array_diagonal(_array_tensor_product(x, A.T, I1), (0, 2))
|
||||
assert _array_diag2contr_diagmatrix(cg).shape == cg.shape
|
||||
assert _array2matrix(cg).shape == cg.shape
|
||||
|
||||
|
||||
def test_arrayexpr_convert_array_to_matrix_support_function():
|
||||
|
||||
assert _support_function_tp1_recognize([], [2 * k]) == 2 * k
|
||||
|
||||
assert _support_function_tp1_recognize([(1, 2)], [A, 2 * k, B, 3]) == 6 * k * A * B
|
||||
|
||||
assert _support_function_tp1_recognize([(0, 3), (1, 2)], [A, B]) == Trace(A * B)
|
||||
|
||||
assert _support_function_tp1_recognize([(1, 2)], [A, B]) == A * B
|
||||
assert _support_function_tp1_recognize([(0, 2)], [A, B]) == A.T * B
|
||||
assert _support_function_tp1_recognize([(1, 3)], [A, B]) == A * B.T
|
||||
assert _support_function_tp1_recognize([(0, 3)], [A, B]) == A.T * B.T
|
||||
|
||||
assert _support_function_tp1_recognize([(1, 2), (5, 6)], [A, B, C, D]) == _array_tensor_product(A * B, C * D)
|
||||
assert _support_function_tp1_recognize([(1, 4), (3, 6)], [A, B, C, D]) == PermuteDims(
|
||||
_array_tensor_product(A * C, B * D), [0, 2, 1, 3])
|
||||
|
||||
assert _support_function_tp1_recognize([(0, 3), (1, 4)], [A, B, C]) == B * A * C
|
||||
|
||||
assert _support_function_tp1_recognize([(9, 10), (1, 2), (5, 6), (3, 4), (7, 8)],
|
||||
[X, Y, A, B, C, D]) == X * Y * A * B * C * D
|
||||
|
||||
assert _support_function_tp1_recognize([(9, 10), (1, 2), (5, 6), (3, 4)],
|
||||
[X, Y, A, B, C, D]) == _array_tensor_product(X * Y * A * B, C * D)
|
||||
|
||||
assert _support_function_tp1_recognize([(1, 7), (3, 8), (4, 11)], [X, Y, A, B, C, D]) == PermuteDims(
|
||||
_array_tensor_product(X * B.T, Y * C, A.T * D.T), [0, 2, 4, 1, 3, 5]
|
||||
)
|
||||
|
||||
assert _support_function_tp1_recognize([(0, 1), (3, 6), (5, 8)], [X, A, B, C, D]) == PermuteDims(
|
||||
_array_tensor_product(Trace(X) * A * C, B * D), [0, 2, 1, 3])
|
||||
|
||||
assert _support_function_tp1_recognize([(1, 2), (3, 4), (5, 6), (7, 8)], [A, A, B, C, D]) == A ** 2 * B * C * D
|
||||
assert _support_function_tp1_recognize([(1, 2), (3, 4), (5, 6), (7, 8)], [X, A, B, C, D]) == X * A * B * C * D
|
||||
|
||||
assert _support_function_tp1_recognize([(1, 6), (3, 8), (5, 10)], [X, Y, A, B, C, D]) == PermuteDims(
|
||||
_array_tensor_product(X * B, Y * C, A * D), [0, 2, 4, 1, 3, 5]
|
||||
)
|
||||
|
||||
assert _support_function_tp1_recognize([(1, 4), (3, 6)], [A, B, C, D]) == PermuteDims(
|
||||
_array_tensor_product(A * C, B * D), [0, 2, 1, 3])
|
||||
|
||||
assert _support_function_tp1_recognize([(0, 4), (1, 7), (2, 5), (3, 8)], [X, A, B, C, D]) == C*X.T*B*A*D
|
||||
|
||||
assert _support_function_tp1_recognize([(0, 4), (1, 7), (2, 5), (3, 8)], [X, A, B, C, D]) == C*X.T*B*A*D
|
||||
|
||||
|
||||
def test_convert_array_to_hadamard_products():
|
||||
|
||||
expr = HadamardProduct(M, N)
|
||||
cg = convert_matrix_to_array(expr)
|
||||
ret = convert_array_to_matrix(cg)
|
||||
assert ret == expr
|
||||
|
||||
expr = HadamardProduct(M, N)*P
|
||||
cg = convert_matrix_to_array(expr)
|
||||
ret = convert_array_to_matrix(cg)
|
||||
assert ret == expr
|
||||
|
||||
expr = Q*HadamardProduct(M, N)*P
|
||||
cg = convert_matrix_to_array(expr)
|
||||
ret = convert_array_to_matrix(cg)
|
||||
assert ret == expr
|
||||
|
||||
expr = Q*HadamardProduct(M, N.T)*P
|
||||
cg = convert_matrix_to_array(expr)
|
||||
ret = convert_array_to_matrix(cg)
|
||||
assert ret == expr
|
||||
|
||||
expr = HadamardProduct(M, N)*HadamardProduct(Q, P)
|
||||
cg = convert_matrix_to_array(expr)
|
||||
ret = convert_array_to_matrix(cg)
|
||||
assert expr == ret
|
||||
|
||||
expr = P.T*HadamardProduct(M, N)*HadamardProduct(Q, P)
|
||||
cg = convert_matrix_to_array(expr)
|
||||
ret = convert_array_to_matrix(cg)
|
||||
assert expr == ret
|
||||
|
||||
# ArrayDiagonal should be converted
|
||||
cg = _array_diagonal(_array_tensor_product(M, N, Q), (1, 3), (0, 2, 4))
|
||||
ret = convert_array_to_matrix(cg)
|
||||
expected = PermuteDims(_array_diagonal(_array_tensor_product(HadamardProduct(M.T, N.T), Q), (1, 2)), [1, 0, 2])
|
||||
assert expected == ret
|
||||
|
||||
# Special case that should return the same expression:
|
||||
cg = _array_diagonal(_array_tensor_product(HadamardProduct(M, N), Q), (0, 2))
|
||||
ret = convert_array_to_matrix(cg)
|
||||
assert ret == cg
|
||||
|
||||
# Hadamard products with traces:
|
||||
|
||||
expr = Trace(HadamardProduct(M, N))
|
||||
cg = convert_matrix_to_array(expr)
|
||||
ret = convert_array_to_matrix(cg)
|
||||
assert ret == Trace(HadamardProduct(M.T, N.T))
|
||||
|
||||
expr = Trace(A*HadamardProduct(M, N))
|
||||
cg = convert_matrix_to_array(expr)
|
||||
ret = convert_array_to_matrix(cg)
|
||||
assert ret == Trace(HadamardProduct(M, N)*A)
|
||||
|
||||
expr = Trace(HadamardProduct(A, M)*N)
|
||||
cg = convert_matrix_to_array(expr)
|
||||
ret = convert_array_to_matrix(cg)
|
||||
assert ret == Trace(HadamardProduct(M.T, N)*A)
|
||||
|
||||
# These should not be converted into Hadamard products:
|
||||
|
||||
cg = _array_diagonal(_array_tensor_product(M, N), (0, 1, 2, 3))
|
||||
ret = convert_array_to_matrix(cg)
|
||||
assert ret == cg
|
||||
|
||||
cg = _array_diagonal(_array_tensor_product(A), (0, 1))
|
||||
ret = convert_array_to_matrix(cg)
|
||||
assert ret == cg
|
||||
|
||||
cg = _array_diagonal(_array_tensor_product(M, N, P), (0, 2, 4), (1, 3, 5))
|
||||
assert convert_array_to_matrix(cg) == HadamardProduct(M, N, P)
|
||||
|
||||
cg = _array_diagonal(_array_tensor_product(M, N, P), (0, 3, 4), (1, 2, 5))
|
||||
assert convert_array_to_matrix(cg) == HadamardProduct(M, P, N.T)
|
||||
|
||||
cg = _array_diagonal(_array_tensor_product(I, I1, x), (1, 4), (3, 5))
|
||||
assert convert_array_to_matrix(cg) == DiagMatrix(x)
|
||||
|
||||
|
||||
def test_identify_removable_identity_matrices():
|
||||
|
||||
D = DiagonalMatrix(MatrixSymbol("D", k, k))
|
||||
|
||||
cg = _array_contraction(_array_tensor_product(A, B, I), (1, 2, 4, 5))
|
||||
expected = _array_contraction(_array_tensor_product(A, B), (1, 2))
|
||||
assert identify_removable_identity_matrices(cg) == expected
|
||||
|
||||
cg = _array_contraction(_array_tensor_product(A, B, C, I), (1, 3, 5, 6, 7))
|
||||
expected = _array_contraction(_array_tensor_product(A, B, C), (1, 3, 5))
|
||||
assert identify_removable_identity_matrices(cg) == expected
|
||||
|
||||
# Tests with diagonal matrices:
|
||||
|
||||
cg = _array_contraction(_array_tensor_product(A, B, D), (1, 2, 4, 5))
|
||||
ret = identify_removable_identity_matrices(cg)
|
||||
expected = _array_contraction(_array_tensor_product(A, B, D), (1, 4), (2, 5))
|
||||
assert ret == expected
|
||||
|
||||
cg = _array_contraction(_array_tensor_product(A, B, D, M, N), (1, 2, 4, 5, 6, 8))
|
||||
ret = identify_removable_identity_matrices(cg)
|
||||
assert ret == cg
|
||||
|
||||
|
||||
def test_combine_removed():
|
||||
|
||||
assert _combine_removed(6, [0, 1, 2], [0, 1, 2]) == [0, 1, 2, 3, 4, 5]
|
||||
assert _combine_removed(8, [2, 5], [1, 3, 4]) == [1, 2, 4, 5, 6]
|
||||
assert _combine_removed(8, [7], []) == [7]
|
||||
|
||||
|
||||
def test_array_contraction_to_diagonal_multiple_identities():
|
||||
|
||||
expr = _array_contraction(_array_tensor_product(A, B, I, C), (1, 2, 4), (5, 6))
|
||||
assert _array_contraction_to_diagonal_multiple_identity(expr) == (expr, [])
|
||||
assert convert_array_to_matrix(expr) == _array_contraction(_array_tensor_product(A, B, C), (1, 2, 4))
|
||||
|
||||
expr = _array_contraction(_array_tensor_product(A, I, I), (1, 2, 4))
|
||||
assert _array_contraction_to_diagonal_multiple_identity(expr) == (A, [2])
|
||||
assert convert_array_to_matrix(expr) == A
|
||||
|
||||
expr = _array_contraction(_array_tensor_product(A, I, I, B), (1, 2, 4), (3, 6))
|
||||
assert _array_contraction_to_diagonal_multiple_identity(expr) == (expr, [])
|
||||
|
||||
expr = _array_contraction(_array_tensor_product(A, I, I, B), (1, 2, 3, 4, 6))
|
||||
assert _array_contraction_to_diagonal_multiple_identity(expr) == (expr, [])
|
||||
|
||||
|
||||
def test_convert_array_element_to_matrix():
|
||||
|
||||
expr = ArrayElement(M, (i, j))
|
||||
assert convert_array_to_matrix(expr) == MatrixElement(M, i, j)
|
||||
|
||||
expr = ArrayElement(_array_contraction(_array_tensor_product(M, N), (1, 3)), (i, j))
|
||||
assert convert_array_to_matrix(expr) == MatrixElement(M*N.T, i, j)
|
||||
|
||||
expr = ArrayElement(_array_tensor_product(M, N), (i, j, m, n))
|
||||
assert convert_array_to_matrix(expr) == expr
|
||||
|
||||
|
||||
def test_convert_array_elementwise_function_to_matrix():
|
||||
|
||||
d = Dummy("d")
|
||||
|
||||
expr = ArrayElementwiseApplyFunc(Lambda(d, sin(d)), x.T*y)
|
||||
assert convert_array_to_matrix(expr) == sin(x.T*y)
|
||||
|
||||
expr = ArrayElementwiseApplyFunc(Lambda(d, d**2), x.T*y)
|
||||
assert convert_array_to_matrix(expr) == (x.T*y)**2
|
||||
|
||||
expr = ArrayElementwiseApplyFunc(Lambda(d, sin(d)), x)
|
||||
assert convert_array_to_matrix(expr).dummy_eq(x.applyfunc(sin))
|
||||
|
||||
expr = ArrayElementwiseApplyFunc(Lambda(d, 1 / (2 * sqrt(d))), x)
|
||||
assert convert_array_to_matrix(expr) == S.Half * HadamardPower(x, -S.Half)
|
||||
|
||||
|
||||
def test_array2matrix():
|
||||
# See issue https://github.com/sympy/sympy/pull/22877
|
||||
expr = PermuteDims(ArrayContraction(ArrayTensorProduct(x, I, I1, x), (0, 3), (1, 7)), Permutation(2, 3))
|
||||
expected = PermuteDims(ArrayTensorProduct(x*x.T, I1), Permutation(3)(1, 2))
|
||||
assert _array2matrix(expr) == expected
|
||||
|
||||
|
||||
def test_recognize_broadcasting():
|
||||
expr = ArrayTensorProduct(x.T*x, A)
|
||||
assert _remove_trivial_dims(expr) == (KroneckerProduct(x.T*x, A), [0, 1])
|
||||
|
||||
expr = ArrayTensorProduct(A, x.T*x)
|
||||
assert _remove_trivial_dims(expr) == (KroneckerProduct(A, x.T*x), [2, 3])
|
||||
|
||||
expr = ArrayTensorProduct(A, B, x.T*x, C)
|
||||
assert _remove_trivial_dims(expr) == (ArrayTensorProduct(A, KroneckerProduct(B, x.T*x), C), [4, 5])
|
||||
|
||||
# Always prefer matrix multiplication to Kronecker product, if possible:
|
||||
expr = ArrayTensorProduct(a, b, x.T*x)
|
||||
assert _remove_trivial_dims(expr) == (a*x.T*x*b.T, [1, 3, 4, 5])
|
||||
@@ -0,0 +1,205 @@
|
||||
from sympy import tanh
|
||||
from sympy.concrete.summations import Sum
|
||||
from sympy.core.symbol import symbols
|
||||
from sympy.functions.special.tensor_functions import KroneckerDelta
|
||||
from sympy.matrices.expressions.matexpr import MatrixSymbol
|
||||
from sympy.matrices.expressions.special import Identity
|
||||
from sympy.tensor.array.expressions import ArrayElementwiseApplyFunc
|
||||
from sympy.tensor.indexed import IndexedBase
|
||||
from sympy.combinatorics import Permutation
|
||||
from sympy.tensor.array.expressions.array_expressions import ArrayContraction, ArrayTensorProduct, \
|
||||
ArrayDiagonal, ArrayAdd, PermuteDims, ArrayElement, _array_tensor_product, _array_contraction, _array_diagonal, \
|
||||
_array_add, _permute_dims, ArraySymbol, OneArray
|
||||
from sympy.tensor.array.expressions.from_array_to_matrix import convert_array_to_matrix
|
||||
from sympy.tensor.array.expressions.from_indexed_to_array import convert_indexed_to_array, _convert_indexed_to_array
|
||||
from sympy.testing.pytest import raises
|
||||
|
||||
|
||||
A, B = symbols("A B", cls=IndexedBase)
|
||||
i, j, k, l, m, n = symbols("i j k l m n")
|
||||
d0, d1, d2, d3 = symbols("d0:4")
|
||||
|
||||
I = Identity(k)
|
||||
|
||||
M = MatrixSymbol("M", k, k)
|
||||
N = MatrixSymbol("N", k, k)
|
||||
P = MatrixSymbol("P", k, k)
|
||||
Q = MatrixSymbol("Q", k, k)
|
||||
|
||||
a = MatrixSymbol("a", k, 1)
|
||||
b = MatrixSymbol("b", k, 1)
|
||||
c = MatrixSymbol("c", k, 1)
|
||||
d = MatrixSymbol("d", k, 1)
|
||||
|
||||
|
||||
def test_arrayexpr_convert_index_to_array_support_function():
|
||||
expr = M[i, j]
|
||||
assert _convert_indexed_to_array(expr) == (M, (i, j))
|
||||
expr = M[i, j]*N[k, l]
|
||||
assert _convert_indexed_to_array(expr) == (ArrayTensorProduct(M, N), (i, j, k, l))
|
||||
expr = M[i, j]*N[j, k]
|
||||
assert _convert_indexed_to_array(expr) == (ArrayDiagonal(ArrayTensorProduct(M, N), (1, 2)), (i, k, j))
|
||||
expr = Sum(M[i, j]*N[j, k], (j, 0, k-1))
|
||||
assert _convert_indexed_to_array(expr) == (ArrayContraction(ArrayTensorProduct(M, N), (1, 2)), (i, k))
|
||||
expr = M[i, j] + N[i, j]
|
||||
assert _convert_indexed_to_array(expr) == (ArrayAdd(M, N), (i, j))
|
||||
expr = M[i, j] + N[j, i]
|
||||
assert _convert_indexed_to_array(expr) == (ArrayAdd(M, PermuteDims(N, Permutation([1, 0]))), (i, j))
|
||||
expr = M[i, j] + M[j, i]
|
||||
assert _convert_indexed_to_array(expr) == (ArrayAdd(M, PermuteDims(M, Permutation([1, 0]))), (i, j))
|
||||
expr = (M*N*P)[i, j]
|
||||
assert _convert_indexed_to_array(expr) == (_array_contraction(ArrayTensorProduct(M, N, P), (1, 2), (3, 4)), (i, j))
|
||||
expr = expr.function # Disregard summation in previous expression
|
||||
ret1, ret2 = _convert_indexed_to_array(expr)
|
||||
assert ret1 == ArrayDiagonal(ArrayTensorProduct(M, N, P), (1, 2), (3, 4))
|
||||
assert str(ret2) == "(i, j, _i_1, _i_2)"
|
||||
expr = KroneckerDelta(i, j)*M[i, k]
|
||||
assert _convert_indexed_to_array(expr) == (M, ({i, j}, k))
|
||||
expr = KroneckerDelta(i, j)*KroneckerDelta(j, k)*M[i, l]
|
||||
assert _convert_indexed_to_array(expr) == (M, ({i, j, k}, l))
|
||||
expr = KroneckerDelta(j, k)*(M[i, j]*N[k, l] + N[i, j]*M[k, l])
|
||||
assert _convert_indexed_to_array(expr) == (_array_diagonal(_array_add(
|
||||
ArrayTensorProduct(M, N),
|
||||
_permute_dims(ArrayTensorProduct(M, N), Permutation(0, 2)(1, 3))
|
||||
), (1, 2)), (i, l, frozenset({j, k})))
|
||||
expr = KroneckerDelta(j, m)*KroneckerDelta(m, k)*(M[i, j]*N[k, l] + N[i, j]*M[k, l])
|
||||
assert _convert_indexed_to_array(expr) == (_array_diagonal(_array_add(
|
||||
ArrayTensorProduct(M, N),
|
||||
_permute_dims(ArrayTensorProduct(M, N), Permutation(0, 2)(1, 3))
|
||||
), (1, 2)), (i, l, frozenset({j, m, k})))
|
||||
expr = KroneckerDelta(i, j)*KroneckerDelta(j, k)*KroneckerDelta(k,m)*M[i, 0]*KroneckerDelta(m, n)
|
||||
assert _convert_indexed_to_array(expr) == (M, ({i, j, k, m, n}, 0))
|
||||
expr = M[i, i]
|
||||
assert _convert_indexed_to_array(expr) == (ArrayDiagonal(M, (0, 1)), (i,))
|
||||
|
||||
|
||||
def test_arrayexpr_convert_indexed_to_array_expression():
|
||||
|
||||
s = Sum(A[i]*B[i], (i, 0, 3))
|
||||
cg = convert_indexed_to_array(s)
|
||||
assert cg == ArrayContraction(ArrayTensorProduct(A, B), (0, 1))
|
||||
|
||||
expr = M*N
|
||||
result = ArrayContraction(ArrayTensorProduct(M, N), (1, 2))
|
||||
elem = expr[i, j]
|
||||
assert convert_indexed_to_array(elem) == result
|
||||
|
||||
expr = M*N*M
|
||||
elem = expr[i, j]
|
||||
result = _array_contraction(_array_tensor_product(M, M, N), (1, 4), (2, 5))
|
||||
cg = convert_indexed_to_array(elem)
|
||||
assert cg == result
|
||||
|
||||
cg = convert_indexed_to_array((M * N * P)[i, j])
|
||||
assert cg == _array_contraction(ArrayTensorProduct(M, N, P), (1, 2), (3, 4))
|
||||
|
||||
cg = convert_indexed_to_array((M * N.T * P)[i, j])
|
||||
assert cg == _array_contraction(ArrayTensorProduct(M, N, P), (1, 3), (2, 4))
|
||||
|
||||
expr = -2*M*N
|
||||
elem = expr[i, j]
|
||||
cg = convert_indexed_to_array(elem)
|
||||
assert cg == ArrayContraction(ArrayTensorProduct(-2, M, N), (1, 2))
|
||||
|
||||
|
||||
def test_arrayexpr_convert_array_element_to_array_expression():
|
||||
A = ArraySymbol("A", (k,))
|
||||
B = ArraySymbol("B", (k,))
|
||||
|
||||
s = Sum(A[i]*B[i], (i, 0, k-1))
|
||||
cg = convert_indexed_to_array(s)
|
||||
assert cg == ArrayContraction(ArrayTensorProduct(A, B), (0, 1))
|
||||
|
||||
s = A[i]*B[i]
|
||||
cg = convert_indexed_to_array(s)
|
||||
assert cg == ArrayDiagonal(ArrayTensorProduct(A, B), (0, 1))
|
||||
|
||||
s = A[i]*B[j]
|
||||
cg = convert_indexed_to_array(s, [i, j])
|
||||
assert cg == ArrayTensorProduct(A, B)
|
||||
cg = convert_indexed_to_array(s, [j, i])
|
||||
assert cg == ArrayTensorProduct(B, A)
|
||||
|
||||
s = tanh(A[i]*B[j])
|
||||
cg = convert_indexed_to_array(s, [i, j])
|
||||
assert cg.dummy_eq(ArrayElementwiseApplyFunc(tanh, ArrayTensorProduct(A, B)))
|
||||
|
||||
|
||||
def test_arrayexpr_convert_indexed_to_array_and_back_to_matrix():
|
||||
|
||||
expr = a.T*b
|
||||
elem = expr[0, 0]
|
||||
cg = convert_indexed_to_array(elem)
|
||||
assert cg == ArrayElement(ArrayContraction(ArrayTensorProduct(a, b), (0, 2)), [0, 0])
|
||||
|
||||
expr = M[i,j] + N[i,j]
|
||||
p1, p2 = _convert_indexed_to_array(expr)
|
||||
assert convert_array_to_matrix(p1) == M + N
|
||||
|
||||
expr = M[i,j] + N[j,i]
|
||||
p1, p2 = _convert_indexed_to_array(expr)
|
||||
assert convert_array_to_matrix(p1) == M + N.T
|
||||
|
||||
expr = M[i,j]*N[k,l] + N[i,j]*M[k,l]
|
||||
p1, p2 = _convert_indexed_to_array(expr)
|
||||
assert convert_array_to_matrix(p1) == ArrayAdd(
|
||||
ArrayTensorProduct(M, N),
|
||||
ArrayTensorProduct(N, M))
|
||||
|
||||
expr = (M*N*P)[i, j]
|
||||
p1, p2 = _convert_indexed_to_array(expr)
|
||||
assert convert_array_to_matrix(p1) == M * N * P
|
||||
|
||||
expr = Sum(M[i,j]*(N*P)[j,m], (j, 0, k-1))
|
||||
p1, p2 = _convert_indexed_to_array(expr)
|
||||
assert convert_array_to_matrix(p1) == M * N * P
|
||||
|
||||
expr = Sum((P[j, m] + P[m, j])*(M[i,j]*N[m,n] + N[i,j]*M[m,n]), (j, 0, k-1), (m, 0, k-1))
|
||||
p1, p2 = _convert_indexed_to_array(expr)
|
||||
assert convert_array_to_matrix(p1) == M * P * N + M * P.T * N + N * P * M + N * P.T * M
|
||||
|
||||
|
||||
def test_arrayexpr_convert_indexed_to_array_out_of_bounds():
|
||||
|
||||
expr = Sum(M[i, i], (i, 0, 4))
|
||||
raises(ValueError, lambda: convert_indexed_to_array(expr))
|
||||
expr = Sum(M[i, i], (i, 0, k))
|
||||
raises(ValueError, lambda: convert_indexed_to_array(expr))
|
||||
expr = Sum(M[i, i], (i, 1, k-1))
|
||||
raises(ValueError, lambda: convert_indexed_to_array(expr))
|
||||
|
||||
expr = Sum(M[i, j]*N[j,m], (j, 0, 4))
|
||||
raises(ValueError, lambda: convert_indexed_to_array(expr))
|
||||
expr = Sum(M[i, j]*N[j,m], (j, 0, k))
|
||||
raises(ValueError, lambda: convert_indexed_to_array(expr))
|
||||
expr = Sum(M[i, j]*N[j,m], (j, 1, k-1))
|
||||
raises(ValueError, lambda: convert_indexed_to_array(expr))
|
||||
|
||||
|
||||
def test_arrayexpr_convert_indexed_to_array_broadcast():
|
||||
A = ArraySymbol("A", (3, 3))
|
||||
B = ArraySymbol("B", (3, 3))
|
||||
|
||||
expr = A[i, j] + B[k, l]
|
||||
O2 = OneArray(3, 3)
|
||||
expected = ArrayAdd(ArrayTensorProduct(A, O2), ArrayTensorProduct(O2, B))
|
||||
assert convert_indexed_to_array(expr) == expected
|
||||
assert convert_indexed_to_array(expr, [i, j, k, l]) == expected
|
||||
assert convert_indexed_to_array(expr, [l, k, i, j]) == ArrayAdd(PermuteDims(ArrayTensorProduct(O2, A), [1, 0, 2, 3]), PermuteDims(ArrayTensorProduct(B, O2), [1, 0, 2, 3]))
|
||||
|
||||
expr = A[i, j] + B[j, k]
|
||||
O1 = OneArray(3)
|
||||
assert convert_indexed_to_array(expr, [i, j, k]) == ArrayAdd(ArrayTensorProduct(A, O1), ArrayTensorProduct(O1, B))
|
||||
|
||||
C = ArraySymbol("C", (d0, d1))
|
||||
D = ArraySymbol("D", (d3, d1))
|
||||
|
||||
expr = C[i, j] + D[k, j]
|
||||
assert convert_indexed_to_array(expr, [i, j, k]) == ArrayAdd(ArrayTensorProduct(C, OneArray(d3)), PermuteDims(ArrayTensorProduct(OneArray(d0), D), [0, 2, 1]))
|
||||
|
||||
X = ArraySymbol("X", (5, 3))
|
||||
|
||||
expr = X[i, n] - X[j, n]
|
||||
assert convert_indexed_to_array(expr, [i, j, n]) == ArrayAdd(ArrayTensorProduct(-1, OneArray(5), X), PermuteDims(ArrayTensorProduct(X, OneArray(5)), [0, 2, 1]))
|
||||
|
||||
raises(ValueError, lambda: convert_indexed_to_array(C[i, j] + D[i, j]))
|
||||
@@ -0,0 +1,128 @@
|
||||
from sympy import Lambda, KroneckerProduct
|
||||
from sympy.core.symbol import symbols, Dummy
|
||||
from sympy.matrices.expressions.hadamard import (HadamardPower, HadamardProduct)
|
||||
from sympy.matrices.expressions.inverse import Inverse
|
||||
from sympy.matrices.expressions.matexpr import MatrixSymbol
|
||||
from sympy.matrices.expressions.matpow import MatPow
|
||||
from sympy.matrices.expressions.special import Identity
|
||||
from sympy.matrices.expressions.trace import Trace
|
||||
from sympy.matrices.expressions.transpose import Transpose
|
||||
from sympy.tensor.array.expressions.array_expressions import ArrayTensorProduct, ArrayContraction, \
|
||||
PermuteDims, ArrayDiagonal, ArrayElementwiseApplyFunc, _array_contraction, _array_tensor_product, Reshape
|
||||
from sympy.tensor.array.expressions.from_array_to_matrix import convert_array_to_matrix
|
||||
from sympy.tensor.array.expressions.from_matrix_to_array import convert_matrix_to_array
|
||||
|
||||
i, j, k, l, m, n = symbols("i j k l m n")
|
||||
|
||||
I = Identity(k)
|
||||
|
||||
M = MatrixSymbol("M", k, k)
|
||||
N = MatrixSymbol("N", k, k)
|
||||
P = MatrixSymbol("P", k, k)
|
||||
Q = MatrixSymbol("Q", k, k)
|
||||
|
||||
A = MatrixSymbol("A", k, k)
|
||||
B = MatrixSymbol("B", k, k)
|
||||
C = MatrixSymbol("C", k, k)
|
||||
D = MatrixSymbol("D", k, k)
|
||||
|
||||
X = MatrixSymbol("X", k, k)
|
||||
Y = MatrixSymbol("Y", k, k)
|
||||
|
||||
a = MatrixSymbol("a", k, 1)
|
||||
b = MatrixSymbol("b", k, 1)
|
||||
c = MatrixSymbol("c", k, 1)
|
||||
d = MatrixSymbol("d", k, 1)
|
||||
|
||||
|
||||
def test_arrayexpr_convert_matrix_to_array():
|
||||
|
||||
expr = M*N
|
||||
result = ArrayContraction(ArrayTensorProduct(M, N), (1, 2))
|
||||
assert convert_matrix_to_array(expr) == result
|
||||
|
||||
expr = M*N*M
|
||||
result = _array_contraction(ArrayTensorProduct(M, N, M), (1, 2), (3, 4))
|
||||
assert convert_matrix_to_array(expr) == result
|
||||
|
||||
expr = Transpose(M)
|
||||
assert convert_matrix_to_array(expr) == PermuteDims(M, [1, 0])
|
||||
|
||||
expr = M*Transpose(N)
|
||||
assert convert_matrix_to_array(expr) == _array_contraction(_array_tensor_product(M, PermuteDims(N, [1, 0])), (1, 2))
|
||||
|
||||
expr = 3*M*N
|
||||
res = convert_matrix_to_array(expr)
|
||||
rexpr = convert_array_to_matrix(res)
|
||||
assert expr == rexpr
|
||||
|
||||
expr = 3*M + N*M.T*M + 4*k*N
|
||||
res = convert_matrix_to_array(expr)
|
||||
rexpr = convert_array_to_matrix(res)
|
||||
assert expr == rexpr
|
||||
|
||||
expr = Inverse(M)*N
|
||||
rexpr = convert_array_to_matrix(convert_matrix_to_array(expr))
|
||||
assert expr == rexpr
|
||||
|
||||
expr = M**2
|
||||
rexpr = convert_array_to_matrix(convert_matrix_to_array(expr))
|
||||
assert expr == rexpr
|
||||
|
||||
expr = M*(2*N + 3*M)
|
||||
res = convert_matrix_to_array(expr)
|
||||
rexpr = convert_array_to_matrix(res)
|
||||
assert expr == rexpr
|
||||
|
||||
expr = Trace(M)
|
||||
result = ArrayContraction(M, (0, 1))
|
||||
assert convert_matrix_to_array(expr) == result
|
||||
|
||||
expr = 3*Trace(M)
|
||||
result = ArrayContraction(ArrayTensorProduct(3, M), (0, 1))
|
||||
assert convert_matrix_to_array(expr) == result
|
||||
|
||||
expr = 3*Trace(Trace(M) * M)
|
||||
result = ArrayContraction(ArrayTensorProduct(3, M, M), (0, 1), (2, 3))
|
||||
assert convert_matrix_to_array(expr) == result
|
||||
|
||||
expr = 3*Trace(M)**2
|
||||
result = ArrayContraction(ArrayTensorProduct(3, M, M), (0, 1), (2, 3))
|
||||
assert convert_matrix_to_array(expr) == result
|
||||
|
||||
expr = HadamardProduct(M, N)
|
||||
result = ArrayDiagonal(ArrayTensorProduct(M, N), (0, 2), (1, 3))
|
||||
assert convert_matrix_to_array(expr) == result
|
||||
|
||||
expr = HadamardProduct(M*N, N*M)
|
||||
result = ArrayDiagonal(ArrayContraction(ArrayTensorProduct(M, N, N, M), (1, 2), (5, 6)), (0, 2), (1, 3))
|
||||
assert convert_matrix_to_array(expr) == result
|
||||
|
||||
expr = HadamardPower(M, 2)
|
||||
result = ArrayDiagonal(ArrayTensorProduct(M, M), (0, 2), (1, 3))
|
||||
assert convert_matrix_to_array(expr) == result
|
||||
|
||||
expr = HadamardPower(M*N, 2)
|
||||
result = ArrayDiagonal(ArrayContraction(ArrayTensorProduct(M, N, M, N), (1, 2), (5, 6)), (0, 2), (1, 3))
|
||||
assert convert_matrix_to_array(expr) == result
|
||||
|
||||
expr = HadamardPower(M, n)
|
||||
d0 = Dummy("d0")
|
||||
result = ArrayElementwiseApplyFunc(Lambda(d0, d0**n), M)
|
||||
assert convert_matrix_to_array(expr).dummy_eq(result)
|
||||
|
||||
expr = M**2
|
||||
assert isinstance(expr, MatPow)
|
||||
assert convert_matrix_to_array(expr) == ArrayContraction(ArrayTensorProduct(M, M), (1, 2))
|
||||
|
||||
expr = a.T*b
|
||||
cg = convert_matrix_to_array(expr)
|
||||
assert cg == ArrayContraction(ArrayTensorProduct(a, b), (0, 2))
|
||||
|
||||
expr = KroneckerProduct(A, B)
|
||||
cg = convert_matrix_to_array(expr)
|
||||
assert cg == Reshape(PermuteDims(ArrayTensorProduct(A, B), [0, 2, 1, 3]), (k**2, k**2))
|
||||
|
||||
expr = KroneckerProduct(A, B, C, D)
|
||||
cg = convert_matrix_to_array(expr)
|
||||
assert cg == Reshape(PermuteDims(ArrayTensorProduct(A, B, C, D), [0, 2, 4, 6, 1, 3, 5, 7]), (k**4, k**4))
|
||||
@@ -0,0 +1,22 @@
|
||||
from sympy import MatrixSymbol, symbols, Sum
|
||||
from sympy.tensor.array.expressions import conv_array_to_indexed, from_array_to_indexed, ArrayTensorProduct, \
|
||||
ArrayContraction, conv_array_to_matrix, from_array_to_matrix, conv_matrix_to_array, from_matrix_to_array, \
|
||||
conv_indexed_to_array, from_indexed_to_array
|
||||
from sympy.testing.pytest import warns
|
||||
from sympy.utilities.exceptions import SymPyDeprecationWarning
|
||||
|
||||
|
||||
def test_deprecated_conv_module_results():
|
||||
|
||||
M = MatrixSymbol("M", 3, 3)
|
||||
N = MatrixSymbol("N", 3, 3)
|
||||
i, j, d = symbols("i j d")
|
||||
|
||||
x = ArrayContraction(ArrayTensorProduct(M, N), (1, 2))
|
||||
y = Sum(M[i, d]*N[d, j], (d, 0, 2))
|
||||
|
||||
with warns(SymPyDeprecationWarning, test_stacklevel=False):
|
||||
assert conv_array_to_indexed.convert_array_to_indexed(x, [i, j]).dummy_eq(from_array_to_indexed.convert_array_to_indexed(x, [i, j]))
|
||||
assert conv_array_to_matrix.convert_array_to_matrix(x) == from_array_to_matrix.convert_array_to_matrix(x)
|
||||
assert conv_matrix_to_array.convert_matrix_to_array(M*N) == from_matrix_to_array.convert_matrix_to_array(M*N)
|
||||
assert conv_indexed_to_array.convert_indexed_to_array(y) == from_indexed_to_array.convert_indexed_to_array(y)
|
||||
@@ -0,0 +1,123 @@
|
||||
import bisect
|
||||
from collections import defaultdict
|
||||
|
||||
from sympy.combinatorics import Permutation
|
||||
from sympy.core.containers import Tuple
|
||||
from sympy.core.numbers import Integer
|
||||
|
||||
|
||||
def _get_mapping_from_subranks(subranks):
|
||||
mapping = {}
|
||||
counter = 0
|
||||
for i, rank in enumerate(subranks):
|
||||
for j in range(rank):
|
||||
mapping[counter] = (i, j)
|
||||
counter += 1
|
||||
return mapping
|
||||
|
||||
|
||||
def _get_contraction_links(args, subranks, *contraction_indices):
|
||||
mapping = _get_mapping_from_subranks(subranks)
|
||||
contraction_tuples = [[mapping[j] for j in i] for i in contraction_indices]
|
||||
dlinks = defaultdict(dict)
|
||||
for links in contraction_tuples:
|
||||
if len(links) == 2:
|
||||
(arg1, pos1), (arg2, pos2) = links
|
||||
dlinks[arg1][pos1] = (arg2, pos2)
|
||||
dlinks[arg2][pos2] = (arg1, pos1)
|
||||
continue
|
||||
|
||||
return args, dict(dlinks)
|
||||
|
||||
|
||||
def _sort_contraction_indices(pairing_indices):
|
||||
pairing_indices = [Tuple(*sorted(i)) for i in pairing_indices]
|
||||
pairing_indices.sort(key=lambda x: min(x))
|
||||
return pairing_indices
|
||||
|
||||
|
||||
def _get_diagonal_indices(flattened_indices):
|
||||
axes_contraction = defaultdict(list)
|
||||
for i, ind in enumerate(flattened_indices):
|
||||
if isinstance(ind, (int, Integer)):
|
||||
# If the indices is a number, there can be no diagonal operation:
|
||||
continue
|
||||
axes_contraction[ind].append(i)
|
||||
axes_contraction = {k: v for k, v in axes_contraction.items() if len(v) > 1}
|
||||
# Put the diagonalized indices at the end:
|
||||
ret_indices = [i for i in flattened_indices if i not in axes_contraction]
|
||||
diag_indices = list(axes_contraction)
|
||||
diag_indices.sort(key=lambda x: flattened_indices.index(x))
|
||||
diagonal_indices = [tuple(axes_contraction[i]) for i in diag_indices]
|
||||
ret_indices += diag_indices
|
||||
ret_indices = tuple(ret_indices)
|
||||
return diagonal_indices, ret_indices
|
||||
|
||||
|
||||
def _get_argindex(subindices, ind):
|
||||
for i, sind in enumerate(subindices):
|
||||
if ind == sind:
|
||||
return i
|
||||
if isinstance(sind, (set, frozenset)) and ind in sind:
|
||||
return i
|
||||
raise IndexError("%s not found in %s" % (ind, subindices))
|
||||
|
||||
|
||||
def _apply_recursively_over_nested_lists(func, arr):
|
||||
if isinstance(arr, (tuple, list, Tuple)):
|
||||
return tuple(_apply_recursively_over_nested_lists(func, i) for i in arr)
|
||||
elif isinstance(arr, Tuple):
|
||||
return Tuple.fromiter(_apply_recursively_over_nested_lists(func, i) for i in arr)
|
||||
else:
|
||||
return func(arr)
|
||||
|
||||
|
||||
def _build_push_indices_up_func_transformation(flattened_contraction_indices):
|
||||
shifts = {0: 0}
|
||||
i = 0
|
||||
cumulative = 0
|
||||
while i < len(flattened_contraction_indices):
|
||||
j = 1
|
||||
while i+j < len(flattened_contraction_indices):
|
||||
if flattened_contraction_indices[i] + j != flattened_contraction_indices[i+j]:
|
||||
break
|
||||
j += 1
|
||||
cumulative += j
|
||||
shifts[flattened_contraction_indices[i]] = cumulative
|
||||
i += j
|
||||
shift_keys = sorted(shifts.keys())
|
||||
|
||||
def func(idx):
|
||||
return shifts[shift_keys[bisect.bisect_right(shift_keys, idx)-1]]
|
||||
|
||||
def transform(j):
|
||||
if j in flattened_contraction_indices:
|
||||
return None
|
||||
else:
|
||||
return j - func(j)
|
||||
|
||||
return transform
|
||||
|
||||
|
||||
def _build_push_indices_down_func_transformation(flattened_contraction_indices):
|
||||
N = flattened_contraction_indices[-1]+2
|
||||
|
||||
shifts = [i for i in range(N) if i not in flattened_contraction_indices]
|
||||
|
||||
def transform(j):
|
||||
if j < len(shifts):
|
||||
return shifts[j]
|
||||
else:
|
||||
return j + shifts[-1] - len(shifts) + 1
|
||||
|
||||
return transform
|
||||
|
||||
|
||||
def _apply_permutation_to_list(perm: Permutation, target_list: list):
|
||||
"""
|
||||
Permute a list according to the given permutation.
|
||||
"""
|
||||
new_list = [None for i in range(perm.size)]
|
||||
for i, e in enumerate(target_list):
|
||||
new_list[perm(i)] = e
|
||||
return new_list
|
||||
@@ -0,0 +1,13 @@
|
||||
from sympy.tensor.array.ndim_array import NDimArray
|
||||
|
||||
|
||||
class MutableNDimArray(NDimArray):
|
||||
|
||||
def as_immutable(self):
|
||||
raise NotImplementedError("abstract method")
|
||||
|
||||
def as_mutable(self):
|
||||
return self
|
||||
|
||||
def _sympy_(self):
|
||||
return self.as_immutable()
|
||||
@@ -0,0 +1,601 @@
|
||||
from sympy.core.basic import Basic
|
||||
from sympy.core.containers import (Dict, Tuple)
|
||||
from sympy.core.expr import Expr
|
||||
from sympy.core.kind import Kind, NumberKind, UndefinedKind
|
||||
from sympy.core.numbers import Integer
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.sympify import sympify
|
||||
from sympy.external.gmpy import SYMPY_INTS
|
||||
from sympy.printing.defaults import Printable
|
||||
|
||||
import itertools
|
||||
from collections.abc import Iterable
|
||||
|
||||
|
||||
class ArrayKind(Kind):
|
||||
"""
|
||||
Kind for N-dimensional array in SymPy.
|
||||
|
||||
This kind represents the multidimensional array that algebraic
|
||||
operations are defined. Basic class for this kind is ``NDimArray``,
|
||||
but any expression representing the array can have this.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
element_kind : Kind
|
||||
Kind of the element. Default is :obj:NumberKind `<sympy.core.kind.NumberKind>`,
|
||||
which means that the array contains only numbers.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
Any instance of array class has ``ArrayKind``.
|
||||
|
||||
>>> from sympy import NDimArray
|
||||
>>> NDimArray([1,2,3]).kind
|
||||
ArrayKind(NumberKind)
|
||||
|
||||
Although expressions representing an array may be not instance of
|
||||
array class, it will have ``ArrayKind`` as well.
|
||||
|
||||
>>> from sympy import Integral
|
||||
>>> from sympy.tensor.array import NDimArray
|
||||
>>> from sympy.abc import x
|
||||
>>> intA = Integral(NDimArray([1,2,3]), x)
|
||||
>>> isinstance(intA, NDimArray)
|
||||
False
|
||||
>>> intA.kind
|
||||
ArrayKind(NumberKind)
|
||||
|
||||
Use ``isinstance()`` to check for ``ArrayKind` without specifying
|
||||
the element kind. Use ``is`` with specifying the element kind.
|
||||
|
||||
>>> from sympy.tensor.array import ArrayKind
|
||||
>>> from sympy.core import NumberKind
|
||||
>>> boolA = NDimArray([True, False])
|
||||
>>> isinstance(boolA.kind, ArrayKind)
|
||||
True
|
||||
>>> boolA.kind is ArrayKind(NumberKind)
|
||||
False
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
shape : Function to return the shape of objects with ``MatrixKind``.
|
||||
|
||||
"""
|
||||
def __new__(cls, element_kind=NumberKind):
|
||||
obj = super().__new__(cls, element_kind)
|
||||
obj.element_kind = element_kind
|
||||
return obj
|
||||
|
||||
def __repr__(self):
|
||||
return "ArrayKind(%s)" % self.element_kind
|
||||
|
||||
@classmethod
|
||||
def _union(cls, kinds) -> 'ArrayKind':
|
||||
elem_kinds = {e.kind for e in kinds}
|
||||
if len(elem_kinds) == 1:
|
||||
elemkind, = elem_kinds
|
||||
else:
|
||||
elemkind = UndefinedKind
|
||||
return ArrayKind(elemkind)
|
||||
|
||||
|
||||
class NDimArray(Printable):
|
||||
"""N-dimensional array.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
Create an N-dim array of zeros:
|
||||
|
||||
>>> from sympy import MutableDenseNDimArray
|
||||
>>> a = MutableDenseNDimArray.zeros(2, 3, 4)
|
||||
>>> a
|
||||
[[[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]]
|
||||
|
||||
Create an N-dim array from a list;
|
||||
|
||||
>>> a = MutableDenseNDimArray([[2, 3], [4, 5]])
|
||||
>>> a
|
||||
[[2, 3], [4, 5]]
|
||||
|
||||
>>> b = MutableDenseNDimArray([[[1, 2], [3, 4], [5, 6]], [[7, 8], [9, 10], [11, 12]]])
|
||||
>>> b
|
||||
[[[1, 2], [3, 4], [5, 6]], [[7, 8], [9, 10], [11, 12]]]
|
||||
|
||||
Create an N-dim array from a flat list with dimension shape:
|
||||
|
||||
>>> a = MutableDenseNDimArray([1, 2, 3, 4, 5, 6], (2, 3))
|
||||
>>> a
|
||||
[[1, 2, 3], [4, 5, 6]]
|
||||
|
||||
Create an N-dim array from a matrix:
|
||||
|
||||
>>> from sympy import Matrix
|
||||
>>> a = Matrix([[1,2],[3,4]])
|
||||
>>> a
|
||||
Matrix([
|
||||
[1, 2],
|
||||
[3, 4]])
|
||||
>>> b = MutableDenseNDimArray(a)
|
||||
>>> b
|
||||
[[1, 2], [3, 4]]
|
||||
|
||||
Arithmetic operations on N-dim arrays
|
||||
|
||||
>>> a = MutableDenseNDimArray([1, 1, 1, 1], (2, 2))
|
||||
>>> b = MutableDenseNDimArray([4, 4, 4, 4], (2, 2))
|
||||
>>> c = a + b
|
||||
>>> c
|
||||
[[5, 5], [5, 5]]
|
||||
>>> a - b
|
||||
[[-3, -3], [-3, -3]]
|
||||
|
||||
"""
|
||||
|
||||
_diff_wrt = True
|
||||
is_scalar = False
|
||||
|
||||
def __new__(cls, iterable, shape=None, **kwargs):
|
||||
from sympy.tensor.array import ImmutableDenseNDimArray
|
||||
return ImmutableDenseNDimArray(iterable, shape, **kwargs)
|
||||
|
||||
def __getitem__(self, index):
|
||||
raise NotImplementedError("A subclass of NDimArray should implement __getitem__")
|
||||
|
||||
def _parse_index(self, index):
|
||||
if isinstance(index, (SYMPY_INTS, Integer)):
|
||||
if index >= self._loop_size:
|
||||
raise ValueError("Only a tuple index is accepted")
|
||||
return index
|
||||
|
||||
if self._loop_size == 0:
|
||||
raise ValueError("Index not valid with an empty array")
|
||||
|
||||
if len(index) != self._rank:
|
||||
raise ValueError('Wrong number of array axes')
|
||||
|
||||
real_index = 0
|
||||
# check if input index can exist in current indexing
|
||||
for i in range(self._rank):
|
||||
if (index[i] >= self.shape[i]) or (index[i] < -self.shape[i]):
|
||||
raise ValueError('Index ' + str(index) + ' out of border')
|
||||
if index[i] < 0:
|
||||
real_index += 1
|
||||
real_index = real_index*self.shape[i] + index[i]
|
||||
|
||||
return real_index
|
||||
|
||||
def _get_tuple_index(self, integer_index):
|
||||
index = []
|
||||
for sh in reversed(self.shape):
|
||||
index.append(integer_index % sh)
|
||||
integer_index //= sh
|
||||
index.reverse()
|
||||
return tuple(index)
|
||||
|
||||
def _check_symbolic_index(self, index):
|
||||
# Check if any index is symbolic:
|
||||
tuple_index = (index if isinstance(index, tuple) else (index,))
|
||||
if any((isinstance(i, Expr) and (not i.is_number)) for i in tuple_index):
|
||||
for i, nth_dim in zip(tuple_index, self.shape):
|
||||
if ((i < 0) == True) or ((i >= nth_dim) == True):
|
||||
raise ValueError("index out of range")
|
||||
from sympy.tensor import Indexed
|
||||
return Indexed(self, *tuple_index)
|
||||
return None
|
||||
|
||||
def _setter_iterable_check(self, value):
|
||||
from sympy.matrices.matrixbase import MatrixBase
|
||||
if isinstance(value, (Iterable, MatrixBase, NDimArray)):
|
||||
raise NotImplementedError
|
||||
|
||||
@classmethod
|
||||
def _scan_iterable_shape(cls, iterable):
|
||||
def f(pointer):
|
||||
if not isinstance(pointer, Iterable):
|
||||
return [pointer], ()
|
||||
|
||||
if len(pointer) == 0:
|
||||
return [], (0,)
|
||||
|
||||
result = []
|
||||
elems, shapes = zip(*[f(i) for i in pointer])
|
||||
if len(set(shapes)) != 1:
|
||||
raise ValueError("could not determine shape unambiguously")
|
||||
for i in elems:
|
||||
result.extend(i)
|
||||
return result, (len(shapes),)+shapes[0]
|
||||
|
||||
return f(iterable)
|
||||
|
||||
@classmethod
|
||||
def _handle_ndarray_creation_inputs(cls, iterable=None, shape=None, **kwargs):
|
||||
from sympy.matrices.matrixbase import MatrixBase
|
||||
from sympy.tensor.array import SparseNDimArray
|
||||
|
||||
if shape is None:
|
||||
if iterable is None:
|
||||
shape = ()
|
||||
iterable = ()
|
||||
# Construction of a sparse array from a sparse array
|
||||
elif isinstance(iterable, SparseNDimArray):
|
||||
return iterable._shape, iterable._sparse_array
|
||||
|
||||
# Construct N-dim array from another N-dim array:
|
||||
elif isinstance(iterable, NDimArray):
|
||||
shape = iterable.shape
|
||||
|
||||
# Construct N-dim array from an iterable (numpy arrays included):
|
||||
elif isinstance(iterable, Iterable):
|
||||
iterable, shape = cls._scan_iterable_shape(iterable)
|
||||
|
||||
# Construct N-dim array from a Matrix:
|
||||
elif isinstance(iterable, MatrixBase):
|
||||
shape = iterable.shape
|
||||
|
||||
else:
|
||||
shape = ()
|
||||
iterable = (iterable,)
|
||||
|
||||
if isinstance(iterable, (Dict, dict)) and shape is not None:
|
||||
new_dict = iterable.copy()
|
||||
for k in new_dict:
|
||||
if isinstance(k, (tuple, Tuple)):
|
||||
new_key = 0
|
||||
for i, idx in enumerate(k):
|
||||
new_key = new_key * shape[i] + idx
|
||||
iterable[new_key] = iterable[k]
|
||||
del iterable[k]
|
||||
|
||||
if isinstance(shape, (SYMPY_INTS, Integer)):
|
||||
shape = (shape,)
|
||||
|
||||
if not all(isinstance(dim, (SYMPY_INTS, Integer)) for dim in shape):
|
||||
raise TypeError("Shape should contain integers only.")
|
||||
|
||||
return tuple(shape), iterable
|
||||
|
||||
def __len__(self):
|
||||
"""Overload common function len(). Returns number of elements in array.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MutableDenseNDimArray
|
||||
>>> a = MutableDenseNDimArray.zeros(3, 3)
|
||||
>>> a
|
||||
[[0, 0, 0], [0, 0, 0], [0, 0, 0]]
|
||||
>>> len(a)
|
||||
9
|
||||
|
||||
"""
|
||||
return self._loop_size
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
"""
|
||||
Returns array shape (dimension).
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MutableDenseNDimArray
|
||||
>>> a = MutableDenseNDimArray.zeros(3, 3)
|
||||
>>> a.shape
|
||||
(3, 3)
|
||||
|
||||
"""
|
||||
return self._shape
|
||||
|
||||
def rank(self):
|
||||
"""
|
||||
Returns rank of array.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MutableDenseNDimArray
|
||||
>>> a = MutableDenseNDimArray.zeros(3,4,5,6,3)
|
||||
>>> a.rank()
|
||||
5
|
||||
|
||||
"""
|
||||
return self._rank
|
||||
|
||||
def diff(self, *args, **kwargs):
|
||||
"""
|
||||
Calculate the derivative of each element in the array.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import ImmutableDenseNDimArray
|
||||
>>> from sympy.abc import x, y
|
||||
>>> M = ImmutableDenseNDimArray([[x, y], [1, x*y]])
|
||||
>>> M.diff(x)
|
||||
[[1, 0], [0, y]]
|
||||
|
||||
"""
|
||||
from sympy.tensor.array.array_derivatives import ArrayDerivative
|
||||
kwargs.setdefault('evaluate', True)
|
||||
return ArrayDerivative(self.as_immutable(), *args, **kwargs)
|
||||
|
||||
def _eval_derivative(self, base):
|
||||
# Types are (base: scalar, self: array)
|
||||
return self.applyfunc(lambda x: base.diff(x))
|
||||
|
||||
def _eval_derivative_n_times(self, s, n):
|
||||
return Basic._eval_derivative_n_times(self, s, n)
|
||||
|
||||
def applyfunc(self, f):
|
||||
"""Apply a function to each element of the N-dim array.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import ImmutableDenseNDimArray
|
||||
>>> m = ImmutableDenseNDimArray([i*2+j for i in range(2) for j in range(2)], (2, 2))
|
||||
>>> m
|
||||
[[0, 1], [2, 3]]
|
||||
>>> m.applyfunc(lambda i: 2*i)
|
||||
[[0, 2], [4, 6]]
|
||||
"""
|
||||
from sympy.tensor.array import SparseNDimArray
|
||||
from sympy.tensor.array.arrayop import Flatten
|
||||
|
||||
if isinstance(self, SparseNDimArray) and f(S.Zero) == 0:
|
||||
return type(self)({k: f(v) for k, v in self._sparse_array.items() if f(v) != 0}, self.shape)
|
||||
|
||||
return type(self)(map(f, Flatten(self)), self.shape)
|
||||
|
||||
def _sympystr(self, printer):
|
||||
def f(sh, shape_left, i, j):
|
||||
if len(shape_left) == 1:
|
||||
return "["+", ".join([printer._print(self[self._get_tuple_index(e)]) for e in range(i, j)])+"]"
|
||||
|
||||
sh //= shape_left[0]
|
||||
return "[" + ", ".join([f(sh, shape_left[1:], i+e*sh, i+(e+1)*sh) for e in range(shape_left[0])]) + "]" # + "\n"*len(shape_left)
|
||||
|
||||
if self.rank() == 0:
|
||||
return printer._print(self[()])
|
||||
if 0 in self.shape:
|
||||
return f"{self.__class__.__name__}([], {self.shape})"
|
||||
return f(self._loop_size, self.shape, 0, self._loop_size)
|
||||
|
||||
def tolist(self):
|
||||
"""
|
||||
Converting MutableDenseNDimArray to one-dim list
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MutableDenseNDimArray
|
||||
>>> a = MutableDenseNDimArray([1, 2, 3, 4], (2, 2))
|
||||
>>> a
|
||||
[[1, 2], [3, 4]]
|
||||
>>> b = a.tolist()
|
||||
>>> b
|
||||
[[1, 2], [3, 4]]
|
||||
"""
|
||||
|
||||
def f(sh, shape_left, i, j):
|
||||
if len(shape_left) == 1:
|
||||
return [self[self._get_tuple_index(e)] for e in range(i, j)]
|
||||
result = []
|
||||
sh //= shape_left[0]
|
||||
for e in range(shape_left[0]):
|
||||
result.append(f(sh, shape_left[1:], i+e*sh, i+(e+1)*sh))
|
||||
return result
|
||||
|
||||
return f(self._loop_size, self.shape, 0, self._loop_size)
|
||||
|
||||
def __add__(self, other):
|
||||
from sympy.tensor.array.arrayop import Flatten
|
||||
|
||||
if not isinstance(other, NDimArray):
|
||||
return NotImplemented
|
||||
|
||||
if self.shape != other.shape:
|
||||
raise ValueError("array shape mismatch")
|
||||
result_list = [i+j for i,j in zip(Flatten(self), Flatten(other))]
|
||||
|
||||
return type(self)(result_list, self.shape)
|
||||
|
||||
def __sub__(self, other):
|
||||
from sympy.tensor.array.arrayop import Flatten
|
||||
|
||||
if not isinstance(other, NDimArray):
|
||||
return NotImplemented
|
||||
|
||||
if self.shape != other.shape:
|
||||
raise ValueError("array shape mismatch")
|
||||
result_list = [i-j for i,j in zip(Flatten(self), Flatten(other))]
|
||||
|
||||
return type(self)(result_list, self.shape)
|
||||
|
||||
def __mul__(self, other):
|
||||
from sympy.matrices.matrixbase import MatrixBase
|
||||
from sympy.tensor.array import SparseNDimArray
|
||||
from sympy.tensor.array.arrayop import Flatten
|
||||
|
||||
if isinstance(other, (Iterable, NDimArray, MatrixBase)):
|
||||
raise ValueError("scalar expected, use tensorproduct(...) for tensorial product")
|
||||
|
||||
other = sympify(other)
|
||||
if isinstance(self, SparseNDimArray):
|
||||
if other.is_zero:
|
||||
return type(self)({}, self.shape)
|
||||
return type(self)({k: other*v for (k, v) in self._sparse_array.items()}, self.shape)
|
||||
|
||||
result_list = [i*other for i in Flatten(self)]
|
||||
return type(self)(result_list, self.shape)
|
||||
|
||||
def __rmul__(self, other):
|
||||
from sympy.matrices.matrixbase import MatrixBase
|
||||
from sympy.tensor.array import SparseNDimArray
|
||||
from sympy.tensor.array.arrayop import Flatten
|
||||
|
||||
if isinstance(other, (Iterable, NDimArray, MatrixBase)):
|
||||
raise ValueError("scalar expected, use tensorproduct(...) for tensorial product")
|
||||
|
||||
other = sympify(other)
|
||||
if isinstance(self, SparseNDimArray):
|
||||
if other.is_zero:
|
||||
return type(self)({}, self.shape)
|
||||
return type(self)({k: other*v for (k, v) in self._sparse_array.items()}, self.shape)
|
||||
|
||||
result_list = [other*i for i in Flatten(self)]
|
||||
return type(self)(result_list, self.shape)
|
||||
|
||||
def __truediv__(self, other):
|
||||
from sympy.matrices.matrixbase import MatrixBase
|
||||
from sympy.tensor.array import SparseNDimArray
|
||||
from sympy.tensor.array.arrayop import Flatten
|
||||
|
||||
if isinstance(other, (Iterable, NDimArray, MatrixBase)):
|
||||
raise ValueError("scalar expected")
|
||||
|
||||
other = sympify(other)
|
||||
if isinstance(self, SparseNDimArray) and other != S.Zero:
|
||||
return type(self)({k: v/other for (k, v) in self._sparse_array.items()}, self.shape)
|
||||
|
||||
result_list = [i/other for i in Flatten(self)]
|
||||
return type(self)(result_list, self.shape)
|
||||
|
||||
def __rtruediv__(self, other):
|
||||
raise NotImplementedError('unsupported operation on NDimArray')
|
||||
|
||||
def __neg__(self):
|
||||
from sympy.tensor.array import SparseNDimArray
|
||||
from sympy.tensor.array.arrayop import Flatten
|
||||
|
||||
if isinstance(self, SparseNDimArray):
|
||||
return type(self)({k: -v for (k, v) in self._sparse_array.items()}, self.shape)
|
||||
|
||||
result_list = [-i for i in Flatten(self)]
|
||||
return type(self)(result_list, self.shape)
|
||||
|
||||
def __iter__(self):
|
||||
def iterator():
|
||||
if self._shape:
|
||||
for i in range(self._shape[0]):
|
||||
yield self[i]
|
||||
else:
|
||||
yield self[()]
|
||||
|
||||
return iterator()
|
||||
|
||||
def __eq__(self, other):
|
||||
"""
|
||||
NDimArray instances can be compared to each other.
|
||||
Instances equal if they have same shape and data.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MutableDenseNDimArray
|
||||
>>> a = MutableDenseNDimArray.zeros(2, 3)
|
||||
>>> b = MutableDenseNDimArray.zeros(2, 3)
|
||||
>>> a == b
|
||||
True
|
||||
>>> c = a.reshape(3, 2)
|
||||
>>> c == b
|
||||
False
|
||||
>>> a[0,0] = 1
|
||||
>>> b[0,0] = 2
|
||||
>>> a == b
|
||||
False
|
||||
"""
|
||||
from sympy.tensor.array import SparseNDimArray
|
||||
if not isinstance(other, NDimArray):
|
||||
return False
|
||||
|
||||
if not self.shape == other.shape:
|
||||
return False
|
||||
|
||||
if isinstance(self, SparseNDimArray) and isinstance(other, SparseNDimArray):
|
||||
return dict(self._sparse_array) == dict(other._sparse_array)
|
||||
|
||||
return list(self) == list(other)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self == other
|
||||
|
||||
def _eval_transpose(self):
|
||||
if self.rank() != 2:
|
||||
raise ValueError("array rank not 2")
|
||||
from .arrayop import permutedims
|
||||
return permutedims(self, (1, 0))
|
||||
|
||||
def transpose(self):
|
||||
return self._eval_transpose()
|
||||
|
||||
def _eval_conjugate(self):
|
||||
from sympy.tensor.array.arrayop import Flatten
|
||||
|
||||
return self.func([i.conjugate() for i in Flatten(self)], self.shape)
|
||||
|
||||
def conjugate(self):
|
||||
return self._eval_conjugate()
|
||||
|
||||
def _eval_adjoint(self):
|
||||
return self.transpose().conjugate()
|
||||
|
||||
def adjoint(self):
|
||||
return self._eval_adjoint()
|
||||
|
||||
def _slice_expand(self, s, dim):
|
||||
if not isinstance(s, slice):
|
||||
return (s,)
|
||||
start, stop, step = s.indices(dim)
|
||||
return [start + i*step for i in range((stop-start)//step)]
|
||||
|
||||
def _get_slice_data_for_array_access(self, index):
|
||||
sl_factors = [self._slice_expand(i, dim) for (i, dim) in zip(index, self.shape)]
|
||||
eindices = itertools.product(*sl_factors)
|
||||
return sl_factors, eindices
|
||||
|
||||
def _get_slice_data_for_array_assignment(self, index, value):
|
||||
if not isinstance(value, NDimArray):
|
||||
value = type(self)(value)
|
||||
sl_factors, eindices = self._get_slice_data_for_array_access(index)
|
||||
slice_offsets = [min(i) if isinstance(i, list) else None for i in sl_factors]
|
||||
# TODO: add checks for dimensions for `value`?
|
||||
return value, eindices, slice_offsets
|
||||
|
||||
@classmethod
|
||||
def _check_special_bounds(cls, flat_list, shape):
|
||||
if shape == () and len(flat_list) != 1:
|
||||
raise ValueError("arrays without shape need one scalar value")
|
||||
if shape == (0,) and len(flat_list) > 0:
|
||||
raise ValueError("if array shape is (0,) there cannot be elements")
|
||||
|
||||
def _check_index_for_getitem(self, index):
|
||||
if isinstance(index, (SYMPY_INTS, Integer, slice)):
|
||||
index = (index,)
|
||||
|
||||
if len(index) < self.rank():
|
||||
index = tuple(index) + \
|
||||
tuple(slice(None) for i in range(len(index), self.rank()))
|
||||
|
||||
if len(index) > self.rank():
|
||||
raise ValueError('Dimension of index greater than rank of array')
|
||||
|
||||
return index
|
||||
|
||||
|
||||
class ImmutableNDimArray(NDimArray, Basic):
|
||||
_op_priority = 11.0
|
||||
|
||||
def __hash__(self):
|
||||
return Basic.__hash__(self)
|
||||
|
||||
def as_immutable(self):
|
||||
return self
|
||||
|
||||
def as_mutable(self):
|
||||
raise NotImplementedError("abstract method")
|
||||
@@ -0,0 +1,196 @@
|
||||
from sympy.core.basic import Basic
|
||||
from sympy.core.containers import (Dict, Tuple)
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.sympify import _sympify
|
||||
from sympy.tensor.array.mutable_ndim_array import MutableNDimArray
|
||||
from sympy.tensor.array.ndim_array import NDimArray, ImmutableNDimArray
|
||||
from sympy.utilities.iterables import flatten
|
||||
|
||||
import functools
|
||||
|
||||
class SparseNDimArray(NDimArray):
|
||||
|
||||
def __new__(self, *args, **kwargs):
|
||||
return ImmutableSparseNDimArray(*args, **kwargs)
|
||||
|
||||
def __getitem__(self, index):
|
||||
"""
|
||||
Get an element from a sparse N-dim array.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MutableSparseNDimArray
|
||||
>>> a = MutableSparseNDimArray(range(4), (2, 2))
|
||||
>>> a
|
||||
[[0, 1], [2, 3]]
|
||||
>>> a[0, 0]
|
||||
0
|
||||
>>> a[1, 1]
|
||||
3
|
||||
>>> a[0]
|
||||
[0, 1]
|
||||
>>> a[1]
|
||||
[2, 3]
|
||||
|
||||
Symbolic indexing:
|
||||
|
||||
>>> from sympy.abc import i, j
|
||||
>>> a[i, j]
|
||||
[[0, 1], [2, 3]][i, j]
|
||||
|
||||
Replace `i` and `j` to get element `(0, 0)`:
|
||||
|
||||
>>> a[i, j].subs({i: 0, j: 0})
|
||||
0
|
||||
|
||||
"""
|
||||
syindex = self._check_symbolic_index(index)
|
||||
if syindex is not None:
|
||||
return syindex
|
||||
|
||||
index = self._check_index_for_getitem(index)
|
||||
|
||||
# `index` is a tuple with one or more slices:
|
||||
if isinstance(index, tuple) and any(isinstance(i, slice) for i in index):
|
||||
sl_factors, eindices = self._get_slice_data_for_array_access(index)
|
||||
array = [self._sparse_array.get(self._parse_index(i), S.Zero) for i in eindices]
|
||||
nshape = [len(el) for i, el in enumerate(sl_factors) if isinstance(index[i], slice)]
|
||||
return type(self)(array, nshape)
|
||||
else:
|
||||
index = self._parse_index(index)
|
||||
return self._sparse_array.get(index, S.Zero)
|
||||
|
||||
@classmethod
|
||||
def zeros(cls, *shape):
|
||||
"""
|
||||
Return a sparse N-dim array of zeros.
|
||||
"""
|
||||
return cls({}, shape)
|
||||
|
||||
def tomatrix(self):
|
||||
"""
|
||||
Converts MutableDenseNDimArray to Matrix. Can convert only 2-dim array, else will raise error.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MutableSparseNDimArray
|
||||
>>> a = MutableSparseNDimArray([1 for i in range(9)], (3, 3))
|
||||
>>> b = a.tomatrix()
|
||||
>>> b
|
||||
Matrix([
|
||||
[1, 1, 1],
|
||||
[1, 1, 1],
|
||||
[1, 1, 1]])
|
||||
"""
|
||||
from sympy.matrices import SparseMatrix
|
||||
if self.rank() != 2:
|
||||
raise ValueError('Dimensions must be of size of 2')
|
||||
|
||||
mat_sparse = {}
|
||||
for key, value in self._sparse_array.items():
|
||||
mat_sparse[self._get_tuple_index(key)] = value
|
||||
|
||||
return SparseMatrix(self.shape[0], self.shape[1], mat_sparse)
|
||||
|
||||
def reshape(self, *newshape):
|
||||
new_total_size = functools.reduce(lambda x,y: x*y, newshape)
|
||||
if new_total_size != self._loop_size:
|
||||
raise ValueError("Invalid reshape parameters " + newshape)
|
||||
|
||||
return type(self)(self._sparse_array, newshape)
|
||||
|
||||
class ImmutableSparseNDimArray(SparseNDimArray, ImmutableNDimArray): # type: ignore
|
||||
|
||||
def __new__(cls, iterable=None, shape=None, **kwargs):
|
||||
shape, flat_list = cls._handle_ndarray_creation_inputs(iterable, shape, **kwargs)
|
||||
shape = Tuple(*map(_sympify, shape))
|
||||
cls._check_special_bounds(flat_list, shape)
|
||||
loop_size = functools.reduce(lambda x,y: x*y, shape) if shape else len(flat_list)
|
||||
|
||||
# Sparse array:
|
||||
if isinstance(flat_list, (dict, Dict)):
|
||||
sparse_array = Dict(flat_list)
|
||||
else:
|
||||
sparse_array = {}
|
||||
for i, el in enumerate(flatten(flat_list)):
|
||||
if el != 0:
|
||||
sparse_array[i] = _sympify(el)
|
||||
|
||||
sparse_array = Dict(sparse_array)
|
||||
|
||||
self = Basic.__new__(cls, sparse_array, shape, **kwargs)
|
||||
self._shape = shape
|
||||
self._rank = len(shape)
|
||||
self._loop_size = loop_size
|
||||
self._sparse_array = sparse_array
|
||||
|
||||
return self
|
||||
|
||||
def __setitem__(self, index, value):
|
||||
raise TypeError("immutable N-dim array")
|
||||
|
||||
def as_mutable(self):
|
||||
return MutableSparseNDimArray(self)
|
||||
|
||||
|
||||
class MutableSparseNDimArray(MutableNDimArray, SparseNDimArray):
|
||||
|
||||
def __new__(cls, iterable=None, shape=None, **kwargs):
|
||||
shape, flat_list = cls._handle_ndarray_creation_inputs(iterable, shape, **kwargs)
|
||||
self = object.__new__(cls)
|
||||
self._shape = shape
|
||||
self._rank = len(shape)
|
||||
self._loop_size = functools.reduce(lambda x,y: x*y, shape) if shape else len(flat_list)
|
||||
|
||||
# Sparse array:
|
||||
if isinstance(flat_list, (dict, Dict)):
|
||||
self._sparse_array = dict(flat_list)
|
||||
return self
|
||||
|
||||
self._sparse_array = {}
|
||||
|
||||
for i, el in enumerate(flatten(flat_list)):
|
||||
if el != 0:
|
||||
self._sparse_array[i] = _sympify(el)
|
||||
|
||||
return self
|
||||
|
||||
def __setitem__(self, index, value):
|
||||
"""Allows to set items to MutableDenseNDimArray.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MutableSparseNDimArray
|
||||
>>> a = MutableSparseNDimArray.zeros(2, 2)
|
||||
>>> a[0, 0] = 1
|
||||
>>> a[1, 1] = 1
|
||||
>>> a
|
||||
[[1, 0], [0, 1]]
|
||||
"""
|
||||
if isinstance(index, tuple) and any(isinstance(i, slice) for i in index):
|
||||
value, eindices, slice_offsets = self._get_slice_data_for_array_assignment(index, value)
|
||||
for i in eindices:
|
||||
other_i = [ind - j for ind, j in zip(i, slice_offsets) if j is not None]
|
||||
other_value = value[other_i]
|
||||
complete_index = self._parse_index(i)
|
||||
if other_value != 0:
|
||||
self._sparse_array[complete_index] = other_value
|
||||
elif complete_index in self._sparse_array:
|
||||
self._sparse_array.pop(complete_index)
|
||||
else:
|
||||
index = self._parse_index(index)
|
||||
value = _sympify(value)
|
||||
if value == 0 and index in self._sparse_array:
|
||||
self._sparse_array.pop(index)
|
||||
else:
|
||||
self._sparse_array[index] = value
|
||||
|
||||
def as_immutable(self):
|
||||
return ImmutableSparseNDimArray(self)
|
||||
|
||||
@property
|
||||
def free_symbols(self):
|
||||
return {i for j in self._sparse_array.values() for i in j.free_symbols}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,78 @@
|
||||
from sympy.tensor.array.array_comprehension import ArrayComprehension, ArrayComprehensionMap
|
||||
from sympy.tensor.array import ImmutableDenseNDimArray
|
||||
from sympy.abc import i, j, k, l
|
||||
from sympy.testing.pytest import raises
|
||||
from sympy.matrices import Matrix
|
||||
|
||||
|
||||
def test_array_comprehension():
|
||||
a = ArrayComprehension(i*j, (i, 1, 3), (j, 2, 4))
|
||||
b = ArrayComprehension(i, (i, 1, j+1))
|
||||
c = ArrayComprehension(i+j+k+l, (i, 1, 2), (j, 1, 3), (k, 1, 4), (l, 1, 5))
|
||||
d = ArrayComprehension(k, (i, 1, 5))
|
||||
e = ArrayComprehension(i, (j, k+1, k+5))
|
||||
assert a.doit().tolist() == [[2, 3, 4], [4, 6, 8], [6, 9, 12]]
|
||||
assert a.shape == (3, 3)
|
||||
assert a.is_shape_numeric == True
|
||||
assert a.tolist() == [[2, 3, 4], [4, 6, 8], [6, 9, 12]]
|
||||
assert a.tomatrix() == Matrix([
|
||||
[2, 3, 4],
|
||||
[4, 6, 8],
|
||||
[6, 9, 12]])
|
||||
assert len(a) == 9
|
||||
assert isinstance(b.doit(), ArrayComprehension)
|
||||
assert isinstance(a.doit(), ImmutableDenseNDimArray)
|
||||
assert b.subs(j, 3) == ArrayComprehension(i, (i, 1, 4))
|
||||
assert b.free_symbols == {j}
|
||||
assert b.shape == (j + 1,)
|
||||
assert b.rank() == 1
|
||||
assert b.is_shape_numeric == False
|
||||
assert c.free_symbols == set()
|
||||
assert c.function == i + j + k + l
|
||||
assert c.limits == ((i, 1, 2), (j, 1, 3), (k, 1, 4), (l, 1, 5))
|
||||
assert c.doit().tolist() == [[[[4, 5, 6, 7, 8], [5, 6, 7, 8, 9], [6, 7, 8, 9, 10], [7, 8, 9, 10, 11]],
|
||||
[[5, 6, 7, 8, 9], [6, 7, 8, 9, 10], [7, 8, 9, 10, 11], [8, 9, 10, 11, 12]],
|
||||
[[6, 7, 8, 9, 10], [7, 8, 9, 10, 11], [8, 9, 10, 11, 12], [9, 10, 11, 12, 13]]],
|
||||
[[[5, 6, 7, 8, 9], [6, 7, 8, 9, 10], [7, 8, 9, 10, 11], [8, 9, 10, 11, 12]],
|
||||
[[6, 7, 8, 9, 10], [7, 8, 9, 10, 11], [8, 9, 10, 11, 12], [9, 10, 11, 12, 13]],
|
||||
[[7, 8, 9, 10, 11], [8, 9, 10, 11, 12], [9, 10, 11, 12, 13], [10, 11, 12, 13, 14]]]]
|
||||
assert c.free_symbols == set()
|
||||
assert c.variables == [i, j, k, l]
|
||||
assert c.bound_symbols == [i, j, k, l]
|
||||
assert d.doit().tolist() == [k, k, k, k, k]
|
||||
assert len(e) == 5
|
||||
raises(TypeError, lambda: ArrayComprehension(i*j, (i, 1, 3), (j, 2, [1, 3, 2])))
|
||||
raises(ValueError, lambda: ArrayComprehension(i*j, (i, 1, 3), (j, 2, 1)))
|
||||
raises(ValueError, lambda: ArrayComprehension(i*j, (i, 1, 3), (j, 2, j+1)))
|
||||
raises(ValueError, lambda: len(ArrayComprehension(i*j, (i, 1, 3), (j, 2, j+4))))
|
||||
raises(TypeError, lambda: ArrayComprehension(i*j, (i, 0, i + 1.5), (j, 0, 2)))
|
||||
raises(ValueError, lambda: b.tolist())
|
||||
raises(ValueError, lambda: b.tomatrix())
|
||||
raises(ValueError, lambda: c.tomatrix())
|
||||
|
||||
def test_arraycomprehensionmap():
|
||||
a = ArrayComprehensionMap(lambda i: i+1, (i, 1, 5))
|
||||
assert a.doit().tolist() == [2, 3, 4, 5, 6]
|
||||
assert a.shape == (5,)
|
||||
assert a.is_shape_numeric
|
||||
assert a.tolist() == [2, 3, 4, 5, 6]
|
||||
assert len(a) == 5
|
||||
assert isinstance(a.doit(), ImmutableDenseNDimArray)
|
||||
expr = ArrayComprehensionMap(lambda i: i+1, (i, 1, k))
|
||||
assert expr.doit() == expr
|
||||
assert expr.subs(k, 4) == ArrayComprehensionMap(lambda i: i+1, (i, 1, 4))
|
||||
assert expr.subs(k, 4).doit() == ImmutableDenseNDimArray([2, 3, 4, 5])
|
||||
b = ArrayComprehensionMap(lambda i: i+1, (i, 1, 2), (i, 1, 3), (i, 1, 4), (i, 1, 5))
|
||||
assert b.doit().tolist() == [[[[2, 3, 4, 5, 6], [3, 5, 7, 9, 11], [4, 7, 10, 13, 16], [5, 9, 13, 17, 21]],
|
||||
[[3, 5, 7, 9, 11], [5, 9, 13, 17, 21], [7, 13, 19, 25, 31], [9, 17, 25, 33, 41]],
|
||||
[[4, 7, 10, 13, 16], [7, 13, 19, 25, 31], [10, 19, 28, 37, 46], [13, 25, 37, 49, 61]]],
|
||||
[[[3, 5, 7, 9, 11], [5, 9, 13, 17, 21], [7, 13, 19, 25, 31], [9, 17, 25, 33, 41]],
|
||||
[[5, 9, 13, 17, 21], [9, 17, 25, 33, 41], [13, 25, 37, 49, 61], [17, 33, 49, 65, 81]],
|
||||
[[7, 13, 19, 25, 31], [13, 25, 37, 49, 61], [19, 37, 55, 73, 91], [25, 49, 73, 97, 121]]]]
|
||||
|
||||
# tests about lambda expression
|
||||
assert ArrayComprehensionMap(lambda: 3, (i, 1, 5)).doit().tolist() == [3, 3, 3, 3, 3]
|
||||
assert ArrayComprehensionMap(lambda i: i+1, (i, 1, 5)).doit().tolist() == [2, 3, 4, 5, 6]
|
||||
raises(ValueError, lambda: ArrayComprehensionMap(i*j, (i, 1, 3), (j, 2, 4)))
|
||||
a = ArrayComprehensionMap(lambda i, j: i+j, (i, 1, 5))
|
||||
raises(ValueError, lambda: a.doit())
|
||||
@@ -0,0 +1,52 @@
|
||||
from sympy.core.symbol import symbols
|
||||
from sympy.matrices.dense import Matrix
|
||||
from sympy.matrices.expressions.matexpr import MatrixSymbol
|
||||
from sympy.tensor.array.ndim_array import NDimArray
|
||||
from sympy.matrices.matrixbase import MatrixBase
|
||||
from sympy.tensor.array.array_derivatives import ArrayDerivative
|
||||
|
||||
x, y, z, t = symbols("x y z t")
|
||||
|
||||
m = Matrix([[x, y], [z, t]])
|
||||
|
||||
M = MatrixSymbol("M", 3, 2)
|
||||
N = MatrixSymbol("N", 4, 3)
|
||||
|
||||
|
||||
def test_array_derivative_construction():
|
||||
|
||||
d = ArrayDerivative(x, m, evaluate=False)
|
||||
assert d.shape == (2, 2)
|
||||
expr = d.doit()
|
||||
assert isinstance(expr, MatrixBase)
|
||||
assert expr.shape == (2, 2)
|
||||
|
||||
d = ArrayDerivative(m, m, evaluate=False)
|
||||
assert d.shape == (2, 2, 2, 2)
|
||||
expr = d.doit()
|
||||
assert isinstance(expr, NDimArray)
|
||||
assert expr.shape == (2, 2, 2, 2)
|
||||
|
||||
d = ArrayDerivative(m, x, evaluate=False)
|
||||
assert d.shape == (2, 2)
|
||||
expr = d.doit()
|
||||
assert isinstance(expr, MatrixBase)
|
||||
assert expr.shape == (2, 2)
|
||||
|
||||
d = ArrayDerivative(M, N, evaluate=False)
|
||||
assert d.shape == (4, 3, 3, 2)
|
||||
expr = d.doit()
|
||||
assert isinstance(expr, ArrayDerivative)
|
||||
assert expr.shape == (4, 3, 3, 2)
|
||||
|
||||
d = ArrayDerivative(M, (N, 2), evaluate=False)
|
||||
assert d.shape == (4, 3, 4, 3, 3, 2)
|
||||
expr = d.doit()
|
||||
assert isinstance(expr, ArrayDerivative)
|
||||
assert expr.shape == (4, 3, 4, 3, 3, 2)
|
||||
|
||||
d = ArrayDerivative(M.as_explicit(), (N.as_explicit(), 2), evaluate=False)
|
||||
assert d.doit().shape == (4, 3, 4, 3, 3, 2)
|
||||
expr = d.doit()
|
||||
assert isinstance(expr, NDimArray)
|
||||
assert expr.shape == (4, 3, 4, 3, 3, 2)
|
||||
@@ -0,0 +1,361 @@
|
||||
import itertools
|
||||
import random
|
||||
|
||||
from sympy.combinatorics import Permutation
|
||||
from sympy.combinatorics.permutations import _af_invert
|
||||
from sympy.testing.pytest import raises
|
||||
|
||||
from sympy.core.function import diff
|
||||
from sympy.core.symbol import symbols
|
||||
from sympy.functions.elementary.complexes import (adjoint, conjugate, transpose)
|
||||
from sympy.functions.elementary.exponential import (exp, log)
|
||||
from sympy.functions.elementary.trigonometric import (cos, sin)
|
||||
from sympy.tensor.array import Array, ImmutableDenseNDimArray, ImmutableSparseNDimArray, MutableSparseNDimArray
|
||||
|
||||
from sympy.tensor.array.arrayop import tensorproduct, tensorcontraction, derive_by_array, permutedims, Flatten, \
|
||||
tensordiagonal
|
||||
|
||||
|
||||
def test_import_NDimArray():
|
||||
from sympy.tensor.array import NDimArray
|
||||
del NDimArray
|
||||
|
||||
|
||||
def test_tensorproduct():
|
||||
x,y,z,t = symbols('x y z t')
|
||||
from sympy.abc import a,b,c,d
|
||||
assert tensorproduct() == 1
|
||||
assert tensorproduct([x]) == Array([x])
|
||||
assert tensorproduct([x], [y]) == Array([[x*y]])
|
||||
assert tensorproduct([x], [y], [z]) == Array([[[x*y*z]]])
|
||||
assert tensorproduct([x], [y], [z], [t]) == Array([[[[x*y*z*t]]]])
|
||||
|
||||
assert tensorproduct(x) == x
|
||||
assert tensorproduct(x, y) == x*y
|
||||
assert tensorproduct(x, y, z) == x*y*z
|
||||
assert tensorproduct(x, y, z, t) == x*y*z*t
|
||||
|
||||
for ArrayType in [ImmutableDenseNDimArray, ImmutableSparseNDimArray]:
|
||||
A = ArrayType([x, y])
|
||||
B = ArrayType([1, 2, 3])
|
||||
C = ArrayType([a, b, c, d])
|
||||
|
||||
assert tensorproduct(A, B, C) == ArrayType([[[a*x, b*x, c*x, d*x], [2*a*x, 2*b*x, 2*c*x, 2*d*x], [3*a*x, 3*b*x, 3*c*x, 3*d*x]],
|
||||
[[a*y, b*y, c*y, d*y], [2*a*y, 2*b*y, 2*c*y, 2*d*y], [3*a*y, 3*b*y, 3*c*y, 3*d*y]]])
|
||||
|
||||
assert tensorproduct([x, y], [1, 2, 3]) == tensorproduct(A, B)
|
||||
|
||||
assert tensorproduct(A, 2) == ArrayType([2*x, 2*y])
|
||||
assert tensorproduct(A, [2]) == ArrayType([[2*x], [2*y]])
|
||||
assert tensorproduct([2], A) == ArrayType([[2*x, 2*y]])
|
||||
assert tensorproduct(a, A) == ArrayType([a*x, a*y])
|
||||
assert tensorproduct(a, A, B) == ArrayType([[a*x, 2*a*x, 3*a*x], [a*y, 2*a*y, 3*a*y]])
|
||||
assert tensorproduct(A, B, a) == ArrayType([[a*x, 2*a*x, 3*a*x], [a*y, 2*a*y, 3*a*y]])
|
||||
assert tensorproduct(B, a, A) == ArrayType([[a*x, a*y], [2*a*x, 2*a*y], [3*a*x, 3*a*y]])
|
||||
|
||||
# tests for large scale sparse array
|
||||
for SparseArrayType in [ImmutableSparseNDimArray, MutableSparseNDimArray]:
|
||||
a = SparseArrayType({1:2, 3:4},(1000, 2000))
|
||||
b = SparseArrayType({1:2, 3:4},(1000, 2000))
|
||||
assert tensorproduct(a, b) == ImmutableSparseNDimArray({2000001: 4, 2000003: 8, 6000001: 8, 6000003: 16}, (1000, 2000, 1000, 2000))
|
||||
|
||||
|
||||
def test_tensorcontraction():
|
||||
from sympy.abc import a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x
|
||||
B = Array(range(18), (2, 3, 3))
|
||||
assert tensorcontraction(B, (1, 2)) == Array([12, 39])
|
||||
C1 = Array([a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x], (2, 3, 2, 2))
|
||||
|
||||
assert tensorcontraction(C1, (0, 2)) == Array([[a + o, b + p], [e + s, f + t], [i + w, j + x]])
|
||||
assert tensorcontraction(C1, (0, 2, 3)) == Array([a + p, e + t, i + x])
|
||||
assert tensorcontraction(C1, (2, 3)) == Array([[a + d, e + h, i + l], [m + p, q + t, u + x]])
|
||||
|
||||
|
||||
def test_derivative_by_array():
|
||||
from sympy.abc import i, j, t, x, y, z
|
||||
|
||||
bexpr = x*y**2*exp(z)*log(t)
|
||||
sexpr = sin(bexpr)
|
||||
cexpr = cos(bexpr)
|
||||
|
||||
a = Array([sexpr])
|
||||
|
||||
assert derive_by_array(sexpr, t) == x*y**2*exp(z)*cos(x*y**2*exp(z)*log(t))/t
|
||||
assert derive_by_array(sexpr, [x, y, z]) == Array([bexpr/x*cexpr, 2*y*bexpr/y**2*cexpr, bexpr*cexpr])
|
||||
assert derive_by_array(a, [x, y, z]) == Array([[bexpr/x*cexpr], [2*y*bexpr/y**2*cexpr], [bexpr*cexpr]])
|
||||
|
||||
assert derive_by_array(sexpr, [[x, y], [z, t]]) == Array([[bexpr/x*cexpr, 2*y*bexpr/y**2*cexpr], [bexpr*cexpr, bexpr/log(t)/t*cexpr]])
|
||||
assert derive_by_array(a, [[x, y], [z, t]]) == Array([[[bexpr/x*cexpr], [2*y*bexpr/y**2*cexpr]], [[bexpr*cexpr], [bexpr/log(t)/t*cexpr]]])
|
||||
assert derive_by_array([[x, y], [z, t]], [x, y]) == Array([[[1, 0], [0, 0]], [[0, 1], [0, 0]]])
|
||||
assert derive_by_array([[x, y], [z, t]], [[x, y], [z, t]]) == Array([[[[1, 0], [0, 0]], [[0, 1], [0, 0]]],
|
||||
[[[0, 0], [1, 0]], [[0, 0], [0, 1]]]])
|
||||
|
||||
assert diff(sexpr, t) == x*y**2*exp(z)*cos(x*y**2*exp(z)*log(t))/t
|
||||
assert diff(sexpr, Array([x, y, z])) == Array([bexpr/x*cexpr, 2*y*bexpr/y**2*cexpr, bexpr*cexpr])
|
||||
assert diff(a, Array([x, y, z])) == Array([[bexpr/x*cexpr], [2*y*bexpr/y**2*cexpr], [bexpr*cexpr]])
|
||||
|
||||
assert diff(sexpr, Array([[x, y], [z, t]])) == Array([[bexpr/x*cexpr, 2*y*bexpr/y**2*cexpr], [bexpr*cexpr, bexpr/log(t)/t*cexpr]])
|
||||
assert diff(a, Array([[x, y], [z, t]])) == Array([[[bexpr/x*cexpr], [2*y*bexpr/y**2*cexpr]], [[bexpr*cexpr], [bexpr/log(t)/t*cexpr]]])
|
||||
assert diff(Array([[x, y], [z, t]]), Array([x, y])) == Array([[[1, 0], [0, 0]], [[0, 1], [0, 0]]])
|
||||
assert diff(Array([[x, y], [z, t]]), Array([[x, y], [z, t]])) == Array([[[[1, 0], [0, 0]], [[0, 1], [0, 0]]],
|
||||
[[[0, 0], [1, 0]], [[0, 0], [0, 1]]]])
|
||||
|
||||
# test for large scale sparse array
|
||||
for SparseArrayType in [ImmutableSparseNDimArray, MutableSparseNDimArray]:
|
||||
b = MutableSparseNDimArray({0:i, 1:j}, (10000, 20000))
|
||||
assert derive_by_array(b, i) == ImmutableSparseNDimArray({0: 1}, (10000, 20000))
|
||||
assert derive_by_array(b, (i, j)) == ImmutableSparseNDimArray({0: 1, 200000001: 1}, (2, 10000, 20000))
|
||||
|
||||
#https://github.com/sympy/sympy/issues/20655
|
||||
U = Array([x, y, z])
|
||||
E = 2
|
||||
assert derive_by_array(E, U) == ImmutableDenseNDimArray([0, 0, 0])
|
||||
|
||||
|
||||
def test_issue_emerged_while_discussing_10972():
|
||||
ua = Array([-1,0])
|
||||
Fa = Array([[0, 1], [-1, 0]])
|
||||
po = tensorproduct(Fa, ua, Fa, ua)
|
||||
assert tensorcontraction(po, (1, 2), (4, 5)) == Array([[0, 0], [0, 1]])
|
||||
|
||||
sa = symbols('a0:144')
|
||||
po = Array(sa, [2, 2, 3, 3, 2, 2])
|
||||
assert tensorcontraction(po, (0, 1), (2, 3), (4, 5)) == sa[0] + sa[108] + sa[111] + sa[124] + sa[127] + sa[140] + sa[143] + sa[16] + sa[19] + sa[3] + sa[32] + sa[35]
|
||||
assert tensorcontraction(po, (0, 1, 4, 5), (2, 3)) == sa[0] + sa[111] + sa[127] + sa[143] + sa[16] + sa[32]
|
||||
assert tensorcontraction(po, (0, 1), (4, 5)) == Array([[sa[0] + sa[108] + sa[111] + sa[3], sa[112] + sa[115] + sa[4] + sa[7],
|
||||
sa[11] + sa[116] + sa[119] + sa[8]], [sa[12] + sa[120] + sa[123] + sa[15],
|
||||
sa[124] + sa[127] + sa[16] + sa[19], sa[128] + sa[131] + sa[20] + sa[23]],
|
||||
[sa[132] + sa[135] + sa[24] + sa[27], sa[136] + sa[139] + sa[28] + sa[31],
|
||||
sa[140] + sa[143] + sa[32] + sa[35]]])
|
||||
assert tensorcontraction(po, (0, 1), (2, 3)) == Array([[sa[0] + sa[108] + sa[124] + sa[140] + sa[16] + sa[32], sa[1] + sa[109] + sa[125] + sa[141] + sa[17] + sa[33]],
|
||||
[sa[110] + sa[126] + sa[142] + sa[18] + sa[2] + sa[34], sa[111] + sa[127] + sa[143] + sa[19] + sa[3] + sa[35]]])
|
||||
|
||||
|
||||
def test_array_permutedims():
|
||||
sa = symbols('a0:144')
|
||||
|
||||
for ArrayType in [ImmutableDenseNDimArray, ImmutableSparseNDimArray]:
|
||||
m1 = ArrayType(sa[:6], (2, 3))
|
||||
assert permutedims(m1, (1, 0)) == transpose(m1)
|
||||
assert m1.tomatrix().T == permutedims(m1, (1, 0)).tomatrix()
|
||||
|
||||
assert m1.tomatrix().T == transpose(m1).tomatrix()
|
||||
assert m1.tomatrix().C == conjugate(m1).tomatrix()
|
||||
assert m1.tomatrix().H == adjoint(m1).tomatrix()
|
||||
|
||||
assert m1.tomatrix().T == m1.transpose().tomatrix()
|
||||
assert m1.tomatrix().C == m1.conjugate().tomatrix()
|
||||
assert m1.tomatrix().H == m1.adjoint().tomatrix()
|
||||
|
||||
raises(ValueError, lambda: permutedims(m1, (0,)))
|
||||
raises(ValueError, lambda: permutedims(m1, (0, 0)))
|
||||
raises(ValueError, lambda: permutedims(m1, (1, 2, 0)))
|
||||
|
||||
# Some tests with random arrays:
|
||||
dims = 6
|
||||
shape = [random.randint(1,5) for i in range(dims)]
|
||||
elems = [random.random() for i in range(tensorproduct(*shape))]
|
||||
ra = ArrayType(elems, shape)
|
||||
perm = list(range(dims))
|
||||
# Randomize the permutation:
|
||||
random.shuffle(perm)
|
||||
# Test inverse permutation:
|
||||
assert permutedims(permutedims(ra, perm), _af_invert(perm)) == ra
|
||||
# Test that permuted shape corresponds to action by `Permutation`:
|
||||
assert permutedims(ra, perm).shape == tuple(Permutation(perm)(shape))
|
||||
|
||||
z = ArrayType.zeros(4,5,6,7)
|
||||
|
||||
assert permutedims(z, (2, 3, 1, 0)).shape == (6, 7, 5, 4)
|
||||
assert permutedims(z, [2, 3, 1, 0]).shape == (6, 7, 5, 4)
|
||||
assert permutedims(z, Permutation([2, 3, 1, 0])).shape == (6, 7, 5, 4)
|
||||
|
||||
po = ArrayType(sa, [2, 2, 3, 3, 2, 2])
|
||||
|
||||
raises(ValueError, lambda: permutedims(po, (1, 1)))
|
||||
raises(ValueError, lambda: po.transpose())
|
||||
raises(ValueError, lambda: po.adjoint())
|
||||
|
||||
assert permutedims(po, reversed(range(po.rank()))) == ArrayType(
|
||||
[[[[[[sa[0], sa[72]], [sa[36], sa[108]]], [[sa[12], sa[84]], [sa[48], sa[120]]], [[sa[24],
|
||||
sa[96]], [sa[60], sa[132]]]],
|
||||
[[[sa[4], sa[76]], [sa[40], sa[112]]], [[sa[16],
|
||||
sa[88]], [sa[52], sa[124]]],
|
||||
[[sa[28], sa[100]], [sa[64], sa[136]]]],
|
||||
[[[sa[8],
|
||||
sa[80]], [sa[44], sa[116]]], [[sa[20], sa[92]], [sa[56], sa[128]]], [[sa[32],
|
||||
sa[104]], [sa[68], sa[140]]]]],
|
||||
[[[[sa[2], sa[74]], [sa[38], sa[110]]], [[sa[14],
|
||||
sa[86]], [sa[50], sa[122]]], [[sa[26], sa[98]], [sa[62], sa[134]]]],
|
||||
[[[sa[6],
|
||||
sa[78]], [sa[42], sa[114]]], [[sa[18], sa[90]], [sa[54], sa[126]]], [[sa[30],
|
||||
sa[102]], [sa[66], sa[138]]]],
|
||||
[[[sa[10], sa[82]], [sa[46], sa[118]]], [[sa[22],
|
||||
sa[94]], [sa[58], sa[130]]],
|
||||
[[sa[34], sa[106]], [sa[70], sa[142]]]]]],
|
||||
[[[[[sa[1],
|
||||
sa[73]], [sa[37], sa[109]]], [[sa[13], sa[85]], [sa[49], sa[121]]], [[sa[25],
|
||||
sa[97]], [sa[61], sa[133]]]],
|
||||
[[[sa[5], sa[77]], [sa[41], sa[113]]], [[sa[17],
|
||||
sa[89]], [sa[53], sa[125]]],
|
||||
[[sa[29], sa[101]], [sa[65], sa[137]]]],
|
||||
[[[sa[9],
|
||||
sa[81]], [sa[45], sa[117]]], [[sa[21], sa[93]], [sa[57], sa[129]]], [[sa[33],
|
||||
sa[105]], [sa[69], sa[141]]]]],
|
||||
[[[[sa[3], sa[75]], [sa[39], sa[111]]], [[sa[15],
|
||||
sa[87]], [sa[51], sa[123]]], [[sa[27], sa[99]], [sa[63], sa[135]]]],
|
||||
[[[sa[7],
|
||||
sa[79]], [sa[43], sa[115]]], [[sa[19], sa[91]], [sa[55], sa[127]]], [[sa[31],
|
||||
sa[103]], [sa[67], sa[139]]]],
|
||||
[[[sa[11], sa[83]], [sa[47], sa[119]]], [[sa[23],
|
||||
sa[95]], [sa[59], sa[131]]],
|
||||
[[sa[35], sa[107]], [sa[71], sa[143]]]]]]])
|
||||
|
||||
assert permutedims(po, (1, 0, 2, 3, 4, 5)) == ArrayType(
|
||||
[[[[[[sa[0], sa[1]], [sa[2], sa[3]]], [[sa[4], sa[5]], [sa[6], sa[7]]], [[sa[8], sa[9]], [sa[10],
|
||||
sa[11]]]],
|
||||
[[[sa[12], sa[13]], [sa[14], sa[15]]], [[sa[16], sa[17]], [sa[18],
|
||||
sa[19]]], [[sa[20], sa[21]], [sa[22], sa[23]]]],
|
||||
[[[sa[24], sa[25]], [sa[26],
|
||||
sa[27]]], [[sa[28], sa[29]], [sa[30], sa[31]]], [[sa[32], sa[33]], [sa[34],
|
||||
sa[35]]]]],
|
||||
[[[[sa[72], sa[73]], [sa[74], sa[75]]], [[sa[76], sa[77]], [sa[78],
|
||||
sa[79]]], [[sa[80], sa[81]], [sa[82], sa[83]]]],
|
||||
[[[sa[84], sa[85]], [sa[86],
|
||||
sa[87]]], [[sa[88], sa[89]], [sa[90], sa[91]]], [[sa[92], sa[93]], [sa[94],
|
||||
sa[95]]]],
|
||||
[[[sa[96], sa[97]], [sa[98], sa[99]]], [[sa[100], sa[101]], [sa[102],
|
||||
sa[103]]],
|
||||
[[sa[104], sa[105]], [sa[106], sa[107]]]]]], [[[[[sa[36], sa[37]], [sa[38],
|
||||
sa[39]]],
|
||||
[[sa[40], sa[41]], [sa[42], sa[43]]],
|
||||
[[sa[44], sa[45]], [sa[46],
|
||||
sa[47]]]],
|
||||
[[[sa[48], sa[49]], [sa[50], sa[51]]],
|
||||
[[sa[52], sa[53]], [sa[54],
|
||||
sa[55]]],
|
||||
[[sa[56], sa[57]], [sa[58], sa[59]]]],
|
||||
[[[sa[60], sa[61]], [sa[62],
|
||||
sa[63]]],
|
||||
[[sa[64], sa[65]], [sa[66], sa[67]]],
|
||||
[[sa[68], sa[69]], [sa[70],
|
||||
sa[71]]]]], [
|
||||
[[[sa[108], sa[109]], [sa[110], sa[111]]],
|
||||
[[sa[112], sa[113]], [sa[114],
|
||||
sa[115]]],
|
||||
[[sa[116], sa[117]], [sa[118], sa[119]]]],
|
||||
[[[sa[120], sa[121]], [sa[122],
|
||||
sa[123]]],
|
||||
[[sa[124], sa[125]], [sa[126], sa[127]]],
|
||||
[[sa[128], sa[129]], [sa[130],
|
||||
sa[131]]]],
|
||||
[[[sa[132], sa[133]], [sa[134], sa[135]]],
|
||||
[[sa[136], sa[137]], [sa[138],
|
||||
sa[139]]],
|
||||
[[sa[140], sa[141]], [sa[142], sa[143]]]]]]])
|
||||
|
||||
assert permutedims(po, (0, 2, 1, 4, 3, 5)) == ArrayType(
|
||||
[[[[[[sa[0], sa[1]], [sa[4], sa[5]], [sa[8], sa[9]]], [[sa[2], sa[3]], [sa[6], sa[7]], [sa[10],
|
||||
sa[11]]]],
|
||||
[[[sa[36], sa[37]], [sa[40], sa[41]], [sa[44], sa[45]]], [[sa[38],
|
||||
sa[39]], [sa[42], sa[43]], [sa[46], sa[47]]]]],
|
||||
[[[[sa[12], sa[13]], [sa[16],
|
||||
sa[17]], [sa[20], sa[21]]], [[sa[14], sa[15]], [sa[18], sa[19]], [sa[22],
|
||||
sa[23]]]],
|
||||
[[[sa[48], sa[49]], [sa[52], sa[53]], [sa[56], sa[57]]], [[sa[50],
|
||||
sa[51]], [sa[54], sa[55]], [sa[58], sa[59]]]]],
|
||||
[[[[sa[24], sa[25]], [sa[28],
|
||||
sa[29]], [sa[32], sa[33]]], [[sa[26], sa[27]], [sa[30], sa[31]], [sa[34],
|
||||
sa[35]]]],
|
||||
[[[sa[60], sa[61]], [sa[64], sa[65]], [sa[68], sa[69]]], [[sa[62],
|
||||
sa[63]], [sa[66], sa[67]], [sa[70], sa[71]]]]]],
|
||||
[[[[[sa[72], sa[73]], [sa[76],
|
||||
sa[77]], [sa[80], sa[81]]], [[sa[74], sa[75]], [sa[78], sa[79]], [sa[82],
|
||||
sa[83]]]],
|
||||
[[[sa[108], sa[109]], [sa[112], sa[113]], [sa[116], sa[117]]], [[sa[110],
|
||||
sa[111]], [sa[114], sa[115]],
|
||||
[sa[118], sa[119]]]]],
|
||||
[[[[sa[84], sa[85]], [sa[88],
|
||||
sa[89]], [sa[92], sa[93]]], [[sa[86], sa[87]], [sa[90], sa[91]], [sa[94],
|
||||
sa[95]]]],
|
||||
[[[sa[120], sa[121]], [sa[124], sa[125]], [sa[128], sa[129]]], [[sa[122],
|
||||
sa[123]], [sa[126], sa[127]],
|
||||
[sa[130], sa[131]]]]],
|
||||
[[[[sa[96], sa[97]], [sa[100],
|
||||
sa[101]], [sa[104], sa[105]]], [[sa[98], sa[99]], [sa[102], sa[103]], [sa[106],
|
||||
sa[107]]]],
|
||||
[[[sa[132], sa[133]], [sa[136], sa[137]], [sa[140], sa[141]]], [[sa[134],
|
||||
sa[135]], [sa[138], sa[139]],
|
||||
[sa[142], sa[143]]]]]]])
|
||||
|
||||
po2 = po.reshape(4, 9, 2, 2)
|
||||
assert po2 == ArrayType([[[[sa[0], sa[1]], [sa[2], sa[3]]], [[sa[4], sa[5]], [sa[6], sa[7]]], [[sa[8], sa[9]], [sa[10], sa[11]]], [[sa[12], sa[13]], [sa[14], sa[15]]], [[sa[16], sa[17]], [sa[18], sa[19]]], [[sa[20], sa[21]], [sa[22], sa[23]]], [[sa[24], sa[25]], [sa[26], sa[27]]], [[sa[28], sa[29]], [sa[30], sa[31]]], [[sa[32], sa[33]], [sa[34], sa[35]]]], [[[sa[36], sa[37]], [sa[38], sa[39]]], [[sa[40], sa[41]], [sa[42], sa[43]]], [[sa[44], sa[45]], [sa[46], sa[47]]], [[sa[48], sa[49]], [sa[50], sa[51]]], [[sa[52], sa[53]], [sa[54], sa[55]]], [[sa[56], sa[57]], [sa[58], sa[59]]], [[sa[60], sa[61]], [sa[62], sa[63]]], [[sa[64], sa[65]], [sa[66], sa[67]]], [[sa[68], sa[69]], [sa[70], sa[71]]]], [[[sa[72], sa[73]], [sa[74], sa[75]]], [[sa[76], sa[77]], [sa[78], sa[79]]], [[sa[80], sa[81]], [sa[82], sa[83]]], [[sa[84], sa[85]], [sa[86], sa[87]]], [[sa[88], sa[89]], [sa[90], sa[91]]], [[sa[92], sa[93]], [sa[94], sa[95]]], [[sa[96], sa[97]], [sa[98], sa[99]]], [[sa[100], sa[101]], [sa[102], sa[103]]], [[sa[104], sa[105]], [sa[106], sa[107]]]], [[[sa[108], sa[109]], [sa[110], sa[111]]], [[sa[112], sa[113]], [sa[114], sa[115]]], [[sa[116], sa[117]], [sa[118], sa[119]]], [[sa[120], sa[121]], [sa[122], sa[123]]], [[sa[124], sa[125]], [sa[126], sa[127]]], [[sa[128], sa[129]], [sa[130], sa[131]]], [[sa[132], sa[133]], [sa[134], sa[135]]], [[sa[136], sa[137]], [sa[138], sa[139]]], [[sa[140], sa[141]], [sa[142], sa[143]]]]])
|
||||
|
||||
assert permutedims(po2, (3, 2, 0, 1)) == ArrayType([[[[sa[0], sa[4], sa[8], sa[12], sa[16], sa[20], sa[24], sa[28], sa[32]], [sa[36], sa[40], sa[44], sa[48], sa[52], sa[56], sa[60], sa[64], sa[68]], [sa[72], sa[76], sa[80], sa[84], sa[88], sa[92], sa[96], sa[100], sa[104]], [sa[108], sa[112], sa[116], sa[120], sa[124], sa[128], sa[132], sa[136], sa[140]]], [[sa[2], sa[6], sa[10], sa[14], sa[18], sa[22], sa[26], sa[30], sa[34]], [sa[38], sa[42], sa[46], sa[50], sa[54], sa[58], sa[62], sa[66], sa[70]], [sa[74], sa[78], sa[82], sa[86], sa[90], sa[94], sa[98], sa[102], sa[106]], [sa[110], sa[114], sa[118], sa[122], sa[126], sa[130], sa[134], sa[138], sa[142]]]], [[[sa[1], sa[5], sa[9], sa[13], sa[17], sa[21], sa[25], sa[29], sa[33]], [sa[37], sa[41], sa[45], sa[49], sa[53], sa[57], sa[61], sa[65], sa[69]], [sa[73], sa[77], sa[81], sa[85], sa[89], sa[93], sa[97], sa[101], sa[105]], [sa[109], sa[113], sa[117], sa[121], sa[125], sa[129], sa[133], sa[137], sa[141]]], [[sa[3], sa[7], sa[11], sa[15], sa[19], sa[23], sa[27], sa[31], sa[35]], [sa[39], sa[43], sa[47], sa[51], sa[55], sa[59], sa[63], sa[67], sa[71]], [sa[75], sa[79], sa[83], sa[87], sa[91], sa[95], sa[99], sa[103], sa[107]], [sa[111], sa[115], sa[119], sa[123], sa[127], sa[131], sa[135], sa[139], sa[143]]]]])
|
||||
|
||||
# test for large scale sparse array
|
||||
for SparseArrayType in [ImmutableSparseNDimArray, MutableSparseNDimArray]:
|
||||
A = SparseArrayType({1:1, 10000:2}, (10000, 20000, 10000))
|
||||
assert permutedims(A, (0, 1, 2)) == A
|
||||
assert permutedims(A, (1, 0, 2)) == SparseArrayType({1: 1, 100000000: 2}, (20000, 10000, 10000))
|
||||
B = SparseArrayType({1:1, 20000:2}, (10000, 20000))
|
||||
assert B.transpose() == SparseArrayType({10000: 1, 1: 2}, (20000, 10000))
|
||||
|
||||
|
||||
def test_permutedims_with_indices():
|
||||
A = Array(range(32)).reshape(2, 2, 2, 2, 2)
|
||||
indices_new = list("abcde")
|
||||
indices_old = list("ebdac")
|
||||
new_A = permutedims(A, index_order_new=indices_new, index_order_old=indices_old)
|
||||
for a, b, c, d, e in itertools.product(range(2), range(2), range(2), range(2), range(2)):
|
||||
assert new_A[a, b, c, d, e] == A[e, b, d, a, c]
|
||||
indices_old = list("cabed")
|
||||
new_A = permutedims(A, index_order_new=indices_new, index_order_old=indices_old)
|
||||
for a, b, c, d, e in itertools.product(range(2), range(2), range(2), range(2), range(2)):
|
||||
assert new_A[a, b, c, d, e] == A[c, a, b, e, d]
|
||||
raises(ValueError, lambda: permutedims(A, index_order_old=list("aacde"), index_order_new=list("abcde")))
|
||||
raises(ValueError, lambda: permutedims(A, index_order_old=list("abcde"), index_order_new=list("abcce")))
|
||||
raises(ValueError, lambda: permutedims(A, index_order_old=list("abcde"), index_order_new=list("abce")))
|
||||
raises(ValueError, lambda: permutedims(A, index_order_old=list("abce"), index_order_new=list("abce")))
|
||||
raises(ValueError, lambda: permutedims(A, [2, 1, 0, 3, 4], index_order_old=list("abcde")))
|
||||
raises(ValueError, lambda: permutedims(A, [2, 1, 0, 3, 4], index_order_new=list("abcde")))
|
||||
|
||||
|
||||
def test_flatten():
|
||||
from sympy.matrices.dense import Matrix
|
||||
for ArrayType in [ImmutableDenseNDimArray, ImmutableSparseNDimArray, Matrix]:
|
||||
A = ArrayType(range(24)).reshape(4, 6)
|
||||
assert list(Flatten(A)) == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23]
|
||||
|
||||
for i, v in enumerate(Flatten(A)):
|
||||
assert i == v
|
||||
|
||||
|
||||
def test_tensordiagonal():
|
||||
from sympy.matrices.dense import eye
|
||||
expr = Array(range(9)).reshape(3, 3)
|
||||
raises(ValueError, lambda: tensordiagonal(expr, [0], [1]))
|
||||
raises(ValueError, lambda: tensordiagonal(expr, [0, 0]))
|
||||
assert tensordiagonal(eye(3), [0, 1]) == Array([1, 1, 1])
|
||||
assert tensordiagonal(expr, [0, 1]) == Array([0, 4, 8])
|
||||
x, y, z = symbols("x y z")
|
||||
expr2 = tensorproduct([x, y, z], expr)
|
||||
assert tensordiagonal(expr2, [1, 2]) == Array([[0, 4*x, 8*x], [0, 4*y, 8*y], [0, 4*z, 8*z]])
|
||||
assert tensordiagonal(expr2, [0, 1]) == Array([[0, 3*y, 6*z], [x, 4*y, 7*z], [2*x, 5*y, 8*z]])
|
||||
assert tensordiagonal(expr2, [0, 1, 2]) == Array([0, 4*y, 8*z])
|
||||
# assert tensordiagonal(expr2, [0]) == permutedims(expr2, [1, 2, 0])
|
||||
# assert tensordiagonal(expr2, [1]) == permutedims(expr2, [0, 2, 1])
|
||||
# assert tensordiagonal(expr2, [2]) == expr2
|
||||
# assert tensordiagonal(expr2, [1], [2]) == expr2
|
||||
# assert tensordiagonal(expr2, [0], [1]) == permutedims(expr2, [2, 0, 1])
|
||||
|
||||
a, b, c, X, Y, Z = symbols("a b c X Y Z")
|
||||
expr3 = tensorproduct([x, y, z], [1, 2, 3], [a, b, c], [X, Y, Z])
|
||||
assert tensordiagonal(expr3, [0, 1, 2, 3]) == Array([x*a*X, 2*y*b*Y, 3*z*c*Z])
|
||||
assert tensordiagonal(expr3, [0, 1], [2, 3]) == tensorproduct([x, 2*y, 3*z], [a*X, b*Y, c*Z])
|
||||
|
||||
# assert tensordiagonal(expr3, [0], [1, 2], [3]) == tensorproduct([x, y, z], [a, 2*b, 3*c], [X, Y, Z])
|
||||
assert tensordiagonal(tensordiagonal(expr3, [2, 3]), [0, 1]) == tensorproduct([a*X, b*Y, c*Z], [x, 2*y, 3*z])
|
||||
|
||||
raises(ValueError, lambda: tensordiagonal([[1, 2, 3], [4, 5, 6]], [0, 1]))
|
||||
raises(ValueError, lambda: tensordiagonal(expr3.reshape(3, 3, 9), [1, 2]))
|
||||
@@ -0,0 +1,452 @@
|
||||
from copy import copy
|
||||
|
||||
from sympy.tensor.array.dense_ndim_array import ImmutableDenseNDimArray
|
||||
from sympy.core.containers import Dict
|
||||
from sympy.core.function import diff
|
||||
from sympy.core.numbers import Rational
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.symbol import (Symbol, symbols)
|
||||
from sympy.matrices import SparseMatrix
|
||||
from sympy.tensor.indexed import (Indexed, IndexedBase)
|
||||
from sympy.matrices import Matrix
|
||||
from sympy.tensor.array.sparse_ndim_array import ImmutableSparseNDimArray
|
||||
from sympy.testing.pytest import raises
|
||||
|
||||
|
||||
def test_ndim_array_initiation():
|
||||
arr_with_no_elements = ImmutableDenseNDimArray([], shape=(0,))
|
||||
assert len(arr_with_no_elements) == 0
|
||||
assert arr_with_no_elements.rank() == 1
|
||||
|
||||
raises(ValueError, lambda: ImmutableDenseNDimArray([0], shape=(0,)))
|
||||
raises(ValueError, lambda: ImmutableDenseNDimArray([1, 2, 3], shape=(0,)))
|
||||
raises(ValueError, lambda: ImmutableDenseNDimArray([], shape=()))
|
||||
|
||||
raises(ValueError, lambda: ImmutableSparseNDimArray([0], shape=(0,)))
|
||||
raises(ValueError, lambda: ImmutableSparseNDimArray([1, 2, 3], shape=(0,)))
|
||||
raises(ValueError, lambda: ImmutableSparseNDimArray([], shape=()))
|
||||
|
||||
arr_with_one_element = ImmutableDenseNDimArray([23])
|
||||
assert len(arr_with_one_element) == 1
|
||||
assert arr_with_one_element[0] == 23
|
||||
assert arr_with_one_element[:] == ImmutableDenseNDimArray([23])
|
||||
assert arr_with_one_element.rank() == 1
|
||||
|
||||
arr_with_symbol_element = ImmutableDenseNDimArray([Symbol('x')])
|
||||
assert len(arr_with_symbol_element) == 1
|
||||
assert arr_with_symbol_element[0] == Symbol('x')
|
||||
assert arr_with_symbol_element[:] == ImmutableDenseNDimArray([Symbol('x')])
|
||||
assert arr_with_symbol_element.rank() == 1
|
||||
|
||||
number5 = 5
|
||||
vector = ImmutableDenseNDimArray.zeros(number5)
|
||||
assert len(vector) == number5
|
||||
assert vector.shape == (number5,)
|
||||
assert vector.rank() == 1
|
||||
|
||||
vector = ImmutableSparseNDimArray.zeros(number5)
|
||||
assert len(vector) == number5
|
||||
assert vector.shape == (number5,)
|
||||
assert vector._sparse_array == Dict()
|
||||
assert vector.rank() == 1
|
||||
|
||||
n_dim_array = ImmutableDenseNDimArray(range(3**4), (3, 3, 3, 3,))
|
||||
assert len(n_dim_array) == 3 * 3 * 3 * 3
|
||||
assert n_dim_array.shape == (3, 3, 3, 3)
|
||||
assert n_dim_array.rank() == 4
|
||||
|
||||
array_shape = (3, 3, 3, 3)
|
||||
sparse_array = ImmutableSparseNDimArray.zeros(*array_shape)
|
||||
assert len(sparse_array._sparse_array) == 0
|
||||
assert len(sparse_array) == 3 * 3 * 3 * 3
|
||||
assert n_dim_array.shape == array_shape
|
||||
assert n_dim_array.rank() == 4
|
||||
|
||||
one_dim_array = ImmutableDenseNDimArray([2, 3, 1])
|
||||
assert len(one_dim_array) == 3
|
||||
assert one_dim_array.shape == (3,)
|
||||
assert one_dim_array.rank() == 1
|
||||
assert one_dim_array.tolist() == [2, 3, 1]
|
||||
|
||||
shape = (3, 3)
|
||||
array_with_many_args = ImmutableSparseNDimArray.zeros(*shape)
|
||||
assert len(array_with_many_args) == 3 * 3
|
||||
assert array_with_many_args.shape == shape
|
||||
assert array_with_many_args[0, 0] == 0
|
||||
assert array_with_many_args.rank() == 2
|
||||
|
||||
shape = (int(3), int(3))
|
||||
array_with_long_shape = ImmutableSparseNDimArray.zeros(*shape)
|
||||
assert len(array_with_long_shape) == 3 * 3
|
||||
assert array_with_long_shape.shape == shape
|
||||
assert array_with_long_shape[int(0), int(0)] == 0
|
||||
assert array_with_long_shape.rank() == 2
|
||||
|
||||
vector_with_long_shape = ImmutableDenseNDimArray(range(5), int(5))
|
||||
assert len(vector_with_long_shape) == 5
|
||||
assert vector_with_long_shape.shape == (int(5),)
|
||||
assert vector_with_long_shape.rank() == 1
|
||||
raises(ValueError, lambda: vector_with_long_shape[int(5)])
|
||||
|
||||
from sympy.abc import x
|
||||
for ArrayType in [ImmutableDenseNDimArray, ImmutableSparseNDimArray]:
|
||||
rank_zero_array = ArrayType(x)
|
||||
assert len(rank_zero_array) == 1
|
||||
assert rank_zero_array.shape == ()
|
||||
assert rank_zero_array.rank() == 0
|
||||
assert rank_zero_array[()] == x
|
||||
raises(ValueError, lambda: rank_zero_array[0])
|
||||
|
||||
|
||||
def test_reshape():
|
||||
array = ImmutableDenseNDimArray(range(50), 50)
|
||||
assert array.shape == (50,)
|
||||
assert array.rank() == 1
|
||||
|
||||
array = array.reshape(5, 5, 2)
|
||||
assert array.shape == (5, 5, 2)
|
||||
assert array.rank() == 3
|
||||
assert len(array) == 50
|
||||
|
||||
|
||||
def test_getitem():
|
||||
for ArrayType in [ImmutableDenseNDimArray, ImmutableSparseNDimArray]:
|
||||
array = ArrayType(range(24)).reshape(2, 3, 4)
|
||||
assert array.tolist() == [[[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]], [[12, 13, 14, 15], [16, 17, 18, 19], [20, 21, 22, 23]]]
|
||||
assert array[0] == ArrayType([[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]])
|
||||
assert array[0, 0] == ArrayType([0, 1, 2, 3])
|
||||
value = 0
|
||||
for i in range(2):
|
||||
for j in range(3):
|
||||
for k in range(4):
|
||||
assert array[i, j, k] == value
|
||||
value += 1
|
||||
|
||||
raises(ValueError, lambda: array[3, 4, 5])
|
||||
raises(ValueError, lambda: array[3, 4, 5, 6])
|
||||
raises(ValueError, lambda: array[3, 4, 5, 3:4])
|
||||
|
||||
|
||||
def test_iterator():
|
||||
array = ImmutableDenseNDimArray(range(4), (2, 2))
|
||||
assert array[0] == ImmutableDenseNDimArray([0, 1])
|
||||
assert array[1] == ImmutableDenseNDimArray([2, 3])
|
||||
|
||||
array = array.reshape(4)
|
||||
j = 0
|
||||
for i in array:
|
||||
assert i == j
|
||||
j += 1
|
||||
|
||||
|
||||
def test_sparse():
|
||||
sparse_array = ImmutableSparseNDimArray([0, 0, 0, 1], (2, 2))
|
||||
assert len(sparse_array) == 2 * 2
|
||||
# dictionary where all data is, only non-zero entries are actually stored:
|
||||
assert len(sparse_array._sparse_array) == 1
|
||||
|
||||
assert sparse_array.tolist() == [[0, 0], [0, 1]]
|
||||
|
||||
for i, j in zip(sparse_array, [[0, 0], [0, 1]]):
|
||||
assert i == ImmutableSparseNDimArray(j)
|
||||
|
||||
def sparse_assignment():
|
||||
sparse_array[0, 0] = 123
|
||||
|
||||
assert len(sparse_array._sparse_array) == 1
|
||||
raises(TypeError, sparse_assignment)
|
||||
assert len(sparse_array._sparse_array) == 1
|
||||
assert sparse_array[0, 0] == 0
|
||||
assert sparse_array/0 == ImmutableSparseNDimArray([[S.NaN, S.NaN], [S.NaN, S.ComplexInfinity]], (2, 2))
|
||||
|
||||
# test for large scale sparse array
|
||||
# equality test
|
||||
assert ImmutableSparseNDimArray.zeros(100000, 200000) == ImmutableSparseNDimArray.zeros(100000, 200000)
|
||||
|
||||
# __mul__ and __rmul__
|
||||
a = ImmutableSparseNDimArray({200001: 1}, (100000, 200000))
|
||||
assert a * 3 == ImmutableSparseNDimArray({200001: 3}, (100000, 200000))
|
||||
assert 3 * a == ImmutableSparseNDimArray({200001: 3}, (100000, 200000))
|
||||
assert a * 0 == ImmutableSparseNDimArray({}, (100000, 200000))
|
||||
assert 0 * a == ImmutableSparseNDimArray({}, (100000, 200000))
|
||||
|
||||
# __truediv__
|
||||
assert a/3 == ImmutableSparseNDimArray({200001: Rational(1, 3)}, (100000, 200000))
|
||||
|
||||
# __neg__
|
||||
assert -a == ImmutableSparseNDimArray({200001: -1}, (100000, 200000))
|
||||
|
||||
|
||||
def test_calculation():
|
||||
|
||||
a = ImmutableDenseNDimArray([1]*9, (3, 3))
|
||||
b = ImmutableDenseNDimArray([9]*9, (3, 3))
|
||||
|
||||
c = a + b
|
||||
for i in c:
|
||||
assert i == ImmutableDenseNDimArray([10, 10, 10])
|
||||
|
||||
assert c == ImmutableDenseNDimArray([10]*9, (3, 3))
|
||||
assert c == ImmutableSparseNDimArray([10]*9, (3, 3))
|
||||
|
||||
c = b - a
|
||||
for i in c:
|
||||
assert i == ImmutableDenseNDimArray([8, 8, 8])
|
||||
|
||||
assert c == ImmutableDenseNDimArray([8]*9, (3, 3))
|
||||
assert c == ImmutableSparseNDimArray([8]*9, (3, 3))
|
||||
|
||||
|
||||
def test_ndim_array_converting():
|
||||
dense_array = ImmutableDenseNDimArray([1, 2, 3, 4], (2, 2))
|
||||
alist = dense_array.tolist()
|
||||
|
||||
assert alist == [[1, 2], [3, 4]]
|
||||
|
||||
matrix = dense_array.tomatrix()
|
||||
assert (isinstance(matrix, Matrix))
|
||||
|
||||
for i in range(len(dense_array)):
|
||||
assert dense_array[dense_array._get_tuple_index(i)] == matrix[i]
|
||||
assert matrix.shape == dense_array.shape
|
||||
|
||||
assert ImmutableDenseNDimArray(matrix) == dense_array
|
||||
assert ImmutableDenseNDimArray(matrix.as_immutable()) == dense_array
|
||||
assert ImmutableDenseNDimArray(matrix.as_mutable()) == dense_array
|
||||
|
||||
sparse_array = ImmutableSparseNDimArray([1, 2, 3, 4], (2, 2))
|
||||
alist = sparse_array.tolist()
|
||||
|
||||
assert alist == [[1, 2], [3, 4]]
|
||||
|
||||
matrix = sparse_array.tomatrix()
|
||||
assert(isinstance(matrix, SparseMatrix))
|
||||
|
||||
for i in range(len(sparse_array)):
|
||||
assert sparse_array[sparse_array._get_tuple_index(i)] == matrix[i]
|
||||
assert matrix.shape == sparse_array.shape
|
||||
|
||||
assert ImmutableSparseNDimArray(matrix) == sparse_array
|
||||
assert ImmutableSparseNDimArray(matrix.as_immutable()) == sparse_array
|
||||
assert ImmutableSparseNDimArray(matrix.as_mutable()) == sparse_array
|
||||
|
||||
|
||||
def test_converting_functions():
|
||||
arr_list = [1, 2, 3, 4]
|
||||
arr_matrix = Matrix(((1, 2), (3, 4)))
|
||||
|
||||
# list
|
||||
arr_ndim_array = ImmutableDenseNDimArray(arr_list, (2, 2))
|
||||
assert (isinstance(arr_ndim_array, ImmutableDenseNDimArray))
|
||||
assert arr_matrix.tolist() == arr_ndim_array.tolist()
|
||||
|
||||
# Matrix
|
||||
arr_ndim_array = ImmutableDenseNDimArray(arr_matrix)
|
||||
assert (isinstance(arr_ndim_array, ImmutableDenseNDimArray))
|
||||
assert arr_matrix.tolist() == arr_ndim_array.tolist()
|
||||
assert arr_matrix.shape == arr_ndim_array.shape
|
||||
|
||||
|
||||
def test_equality():
|
||||
first_list = [1, 2, 3, 4]
|
||||
second_list = [1, 2, 3, 4]
|
||||
third_list = [4, 3, 2, 1]
|
||||
assert first_list == second_list
|
||||
assert first_list != third_list
|
||||
|
||||
first_ndim_array = ImmutableDenseNDimArray(first_list, (2, 2))
|
||||
second_ndim_array = ImmutableDenseNDimArray(second_list, (2, 2))
|
||||
fourth_ndim_array = ImmutableDenseNDimArray(first_list, (2, 2))
|
||||
|
||||
assert first_ndim_array == second_ndim_array
|
||||
|
||||
def assignment_attempt(a):
|
||||
a[0, 0] = 0
|
||||
|
||||
raises(TypeError, lambda: assignment_attempt(second_ndim_array))
|
||||
assert first_ndim_array == second_ndim_array
|
||||
assert first_ndim_array == fourth_ndim_array
|
||||
|
||||
|
||||
def test_arithmetic():
|
||||
a = ImmutableDenseNDimArray([3 for i in range(9)], (3, 3))
|
||||
b = ImmutableDenseNDimArray([7 for i in range(9)], (3, 3))
|
||||
|
||||
c1 = a + b
|
||||
c2 = b + a
|
||||
assert c1 == c2
|
||||
|
||||
d1 = a - b
|
||||
d2 = b - a
|
||||
assert d1 == d2 * (-1)
|
||||
|
||||
e1 = a * 5
|
||||
e2 = 5 * a
|
||||
e3 = copy(a)
|
||||
e3 *= 5
|
||||
assert e1 == e2 == e3
|
||||
|
||||
f1 = a / 5
|
||||
f2 = copy(a)
|
||||
f2 /= 5
|
||||
assert f1 == f2
|
||||
assert f1[0, 0] == f1[0, 1] == f1[0, 2] == f1[1, 0] == f1[1, 1] == \
|
||||
f1[1, 2] == f1[2, 0] == f1[2, 1] == f1[2, 2] == Rational(3, 5)
|
||||
|
||||
assert type(a) == type(b) == type(c1) == type(c2) == type(d1) == type(d2) \
|
||||
== type(e1) == type(e2) == type(e3) == type(f1)
|
||||
|
||||
z0 = -a
|
||||
assert z0 == ImmutableDenseNDimArray([-3 for i in range(9)], (3, 3))
|
||||
|
||||
|
||||
def test_higher_dimenions():
|
||||
m3 = ImmutableDenseNDimArray(range(10, 34), (2, 3, 4))
|
||||
|
||||
assert m3.tolist() == [[[10, 11, 12, 13],
|
||||
[14, 15, 16, 17],
|
||||
[18, 19, 20, 21]],
|
||||
|
||||
[[22, 23, 24, 25],
|
||||
[26, 27, 28, 29],
|
||||
[30, 31, 32, 33]]]
|
||||
|
||||
assert m3._get_tuple_index(0) == (0, 0, 0)
|
||||
assert m3._get_tuple_index(1) == (0, 0, 1)
|
||||
assert m3._get_tuple_index(4) == (0, 1, 0)
|
||||
assert m3._get_tuple_index(12) == (1, 0, 0)
|
||||
|
||||
assert str(m3) == '[[[10, 11, 12, 13], [14, 15, 16, 17], [18, 19, 20, 21]], [[22, 23, 24, 25], [26, 27, 28, 29], [30, 31, 32, 33]]]'
|
||||
|
||||
m3_rebuilt = ImmutableDenseNDimArray([[[10, 11, 12, 13], [14, 15, 16, 17], [18, 19, 20, 21]], [[22, 23, 24, 25], [26, 27, 28, 29], [30, 31, 32, 33]]])
|
||||
assert m3 == m3_rebuilt
|
||||
|
||||
m3_other = ImmutableDenseNDimArray([[[10, 11, 12, 13], [14, 15, 16, 17], [18, 19, 20, 21]], [[22, 23, 24, 25], [26, 27, 28, 29], [30, 31, 32, 33]]], (2, 3, 4))
|
||||
|
||||
assert m3 == m3_other
|
||||
|
||||
|
||||
def test_rebuild_immutable_arrays():
|
||||
sparr = ImmutableSparseNDimArray(range(10, 34), (2, 3, 4))
|
||||
densarr = ImmutableDenseNDimArray(range(10, 34), (2, 3, 4))
|
||||
|
||||
assert sparr == sparr.func(*sparr.args)
|
||||
assert densarr == densarr.func(*densarr.args)
|
||||
|
||||
|
||||
def test_slices():
|
||||
md = ImmutableDenseNDimArray(range(10, 34), (2, 3, 4))
|
||||
|
||||
assert md[:] == ImmutableDenseNDimArray(range(10, 34), (2, 3, 4))
|
||||
assert md[:, :, 0].tomatrix() == Matrix([[10, 14, 18], [22, 26, 30]])
|
||||
assert md[0, 1:2, :].tomatrix() == Matrix([[14, 15, 16, 17]])
|
||||
assert md[0, 1:3, :].tomatrix() == Matrix([[14, 15, 16, 17], [18, 19, 20, 21]])
|
||||
assert md[:, :, :] == md
|
||||
|
||||
sd = ImmutableSparseNDimArray(range(10, 34), (2, 3, 4))
|
||||
assert sd == ImmutableSparseNDimArray(md)
|
||||
|
||||
assert sd[:] == ImmutableSparseNDimArray(range(10, 34), (2, 3, 4))
|
||||
assert sd[:, :, 0].tomatrix() == Matrix([[10, 14, 18], [22, 26, 30]])
|
||||
assert sd[0, 1:2, :].tomatrix() == Matrix([[14, 15, 16, 17]])
|
||||
assert sd[0, 1:3, :].tomatrix() == Matrix([[14, 15, 16, 17], [18, 19, 20, 21]])
|
||||
assert sd[:, :, :] == sd
|
||||
|
||||
|
||||
def test_diff_and_applyfunc():
|
||||
from sympy.abc import x, y, z
|
||||
md = ImmutableDenseNDimArray([[x, y], [x*z, x*y*z]])
|
||||
assert md.diff(x) == ImmutableDenseNDimArray([[1, 0], [z, y*z]])
|
||||
assert diff(md, x) == ImmutableDenseNDimArray([[1, 0], [z, y*z]])
|
||||
|
||||
sd = ImmutableSparseNDimArray(md)
|
||||
assert sd == ImmutableSparseNDimArray([x, y, x*z, x*y*z], (2, 2))
|
||||
assert sd.diff(x) == ImmutableSparseNDimArray([[1, 0], [z, y*z]])
|
||||
assert diff(sd, x) == ImmutableSparseNDimArray([[1, 0], [z, y*z]])
|
||||
|
||||
mdn = md.applyfunc(lambda x: x*3)
|
||||
assert mdn == ImmutableDenseNDimArray([[3*x, 3*y], [3*x*z, 3*x*y*z]])
|
||||
assert md != mdn
|
||||
|
||||
sdn = sd.applyfunc(lambda x: x/2)
|
||||
assert sdn == ImmutableSparseNDimArray([[x/2, y/2], [x*z/2, x*y*z/2]])
|
||||
assert sd != sdn
|
||||
|
||||
sdp = sd.applyfunc(lambda x: x+1)
|
||||
assert sdp == ImmutableSparseNDimArray([[x + 1, y + 1], [x*z + 1, x*y*z + 1]])
|
||||
assert sd != sdp
|
||||
|
||||
|
||||
def test_op_priority():
|
||||
from sympy.abc import x
|
||||
md = ImmutableDenseNDimArray([1, 2, 3])
|
||||
e1 = (1+x)*md
|
||||
e2 = md*(1+x)
|
||||
assert e1 == ImmutableDenseNDimArray([1+x, 2+2*x, 3+3*x])
|
||||
assert e1 == e2
|
||||
|
||||
sd = ImmutableSparseNDimArray([1, 2, 3])
|
||||
e3 = (1+x)*sd
|
||||
e4 = sd*(1+x)
|
||||
assert e3 == ImmutableDenseNDimArray([1+x, 2+2*x, 3+3*x])
|
||||
assert e3 == e4
|
||||
|
||||
|
||||
def test_symbolic_indexing():
|
||||
x, y, z, w = symbols("x y z w")
|
||||
M = ImmutableDenseNDimArray([[x, y], [z, w]])
|
||||
i, j = symbols("i, j")
|
||||
Mij = M[i, j]
|
||||
assert isinstance(Mij, Indexed)
|
||||
Ms = ImmutableSparseNDimArray([[2, 3*x], [4, 5]])
|
||||
msij = Ms[i, j]
|
||||
assert isinstance(msij, Indexed)
|
||||
for oi, oj in [(0, 0), (0, 1), (1, 0), (1, 1)]:
|
||||
assert Mij.subs({i: oi, j: oj}) == M[oi, oj]
|
||||
assert msij.subs({i: oi, j: oj}) == Ms[oi, oj]
|
||||
A = IndexedBase("A", (0, 2))
|
||||
assert A[0, 0].subs(A, M) == x
|
||||
assert A[i, j].subs(A, M) == M[i, j]
|
||||
assert M[i, j].subs(M, A) == A[i, j]
|
||||
|
||||
assert isinstance(M[3 * i - 2, j], Indexed)
|
||||
assert M[3 * i - 2, j].subs({i: 1, j: 0}) == M[1, 0]
|
||||
assert isinstance(M[i, 0], Indexed)
|
||||
assert M[i, 0].subs(i, 0) == M[0, 0]
|
||||
assert M[0, i].subs(i, 1) == M[0, 1]
|
||||
|
||||
assert M[i, j].diff(x) == ImmutableDenseNDimArray([[1, 0], [0, 0]])[i, j]
|
||||
assert Ms[i, j].diff(x) == ImmutableSparseNDimArray([[0, 3], [0, 0]])[i, j]
|
||||
|
||||
Mo = ImmutableDenseNDimArray([1, 2, 3])
|
||||
assert Mo[i].subs(i, 1) == 2
|
||||
Mos = ImmutableSparseNDimArray([1, 2, 3])
|
||||
assert Mos[i].subs(i, 1) == 2
|
||||
|
||||
raises(ValueError, lambda: M[i, 2])
|
||||
raises(ValueError, lambda: M[i, -1])
|
||||
raises(ValueError, lambda: M[2, i])
|
||||
raises(ValueError, lambda: M[-1, i])
|
||||
|
||||
raises(ValueError, lambda: Ms[i, 2])
|
||||
raises(ValueError, lambda: Ms[i, -1])
|
||||
raises(ValueError, lambda: Ms[2, i])
|
||||
raises(ValueError, lambda: Ms[-1, i])
|
||||
|
||||
|
||||
def test_issue_12665():
|
||||
# Testing Python 3 hash of immutable arrays:
|
||||
arr = ImmutableDenseNDimArray([1, 2, 3])
|
||||
# This should NOT raise an exception:
|
||||
hash(arr)
|
||||
|
||||
|
||||
def test_zeros_without_shape():
|
||||
arr = ImmutableDenseNDimArray.zeros()
|
||||
assert arr == ImmutableDenseNDimArray(0)
|
||||
|
||||
def test_issue_21870():
|
||||
a0 = ImmutableDenseNDimArray(0)
|
||||
assert a0.rank() == 0
|
||||
a1 = ImmutableDenseNDimArray(a0)
|
||||
assert a1.rank() == 0
|
||||
@@ -0,0 +1,374 @@
|
||||
from copy import copy
|
||||
|
||||
from sympy.tensor.array.dense_ndim_array import MutableDenseNDimArray
|
||||
from sympy.core.function import diff
|
||||
from sympy.core.numbers import Rational
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.symbol import Symbol
|
||||
from sympy.core.sympify import sympify
|
||||
from sympy.matrices import SparseMatrix
|
||||
from sympy.matrices import Matrix
|
||||
from sympy.tensor.array.sparse_ndim_array import MutableSparseNDimArray
|
||||
from sympy.testing.pytest import raises
|
||||
|
||||
|
||||
def test_ndim_array_initiation():
|
||||
arr_with_one_element = MutableDenseNDimArray([23])
|
||||
assert len(arr_with_one_element) == 1
|
||||
assert arr_with_one_element[0] == 23
|
||||
assert arr_with_one_element.rank() == 1
|
||||
raises(ValueError, lambda: arr_with_one_element[1])
|
||||
|
||||
arr_with_symbol_element = MutableDenseNDimArray([Symbol('x')])
|
||||
assert len(arr_with_symbol_element) == 1
|
||||
assert arr_with_symbol_element[0] == Symbol('x')
|
||||
assert arr_with_symbol_element.rank() == 1
|
||||
|
||||
number5 = 5
|
||||
vector = MutableDenseNDimArray.zeros(number5)
|
||||
assert len(vector) == number5
|
||||
assert vector.shape == (number5,)
|
||||
assert vector.rank() == 1
|
||||
raises(ValueError, lambda: arr_with_one_element[5])
|
||||
|
||||
vector = MutableSparseNDimArray.zeros(number5)
|
||||
assert len(vector) == number5
|
||||
assert vector.shape == (number5,)
|
||||
assert vector._sparse_array == {}
|
||||
assert vector.rank() == 1
|
||||
|
||||
n_dim_array = MutableDenseNDimArray(range(3**4), (3, 3, 3, 3,))
|
||||
assert len(n_dim_array) == 3 * 3 * 3 * 3
|
||||
assert n_dim_array.shape == (3, 3, 3, 3)
|
||||
assert n_dim_array.rank() == 4
|
||||
raises(ValueError, lambda: n_dim_array[0, 0, 0, 3])
|
||||
raises(ValueError, lambda: n_dim_array[3, 0, 0, 0])
|
||||
raises(ValueError, lambda: n_dim_array[3**4])
|
||||
|
||||
array_shape = (3, 3, 3, 3)
|
||||
sparse_array = MutableSparseNDimArray.zeros(*array_shape)
|
||||
assert len(sparse_array._sparse_array) == 0
|
||||
assert len(sparse_array) == 3 * 3 * 3 * 3
|
||||
assert n_dim_array.shape == array_shape
|
||||
assert n_dim_array.rank() == 4
|
||||
|
||||
one_dim_array = MutableDenseNDimArray([2, 3, 1])
|
||||
assert len(one_dim_array) == 3
|
||||
assert one_dim_array.shape == (3,)
|
||||
assert one_dim_array.rank() == 1
|
||||
assert one_dim_array.tolist() == [2, 3, 1]
|
||||
|
||||
shape = (3, 3)
|
||||
array_with_many_args = MutableSparseNDimArray.zeros(*shape)
|
||||
assert len(array_with_many_args) == 3 * 3
|
||||
assert array_with_many_args.shape == shape
|
||||
assert array_with_many_args[0, 0] == 0
|
||||
assert array_with_many_args.rank() == 2
|
||||
|
||||
shape = (int(3), int(3))
|
||||
array_with_long_shape = MutableSparseNDimArray.zeros(*shape)
|
||||
assert len(array_with_long_shape) == 3 * 3
|
||||
assert array_with_long_shape.shape == shape
|
||||
assert array_with_long_shape[int(0), int(0)] == 0
|
||||
assert array_with_long_shape.rank() == 2
|
||||
|
||||
vector_with_long_shape = MutableDenseNDimArray(range(5), int(5))
|
||||
assert len(vector_with_long_shape) == 5
|
||||
assert vector_with_long_shape.shape == (int(5),)
|
||||
assert vector_with_long_shape.rank() == 1
|
||||
raises(ValueError, lambda: vector_with_long_shape[int(5)])
|
||||
|
||||
from sympy.abc import x
|
||||
for ArrayType in [MutableDenseNDimArray, MutableSparseNDimArray]:
|
||||
rank_zero_array = ArrayType(x)
|
||||
assert len(rank_zero_array) == 1
|
||||
assert rank_zero_array.shape == ()
|
||||
assert rank_zero_array.rank() == 0
|
||||
assert rank_zero_array[()] == x
|
||||
raises(ValueError, lambda: rank_zero_array[0])
|
||||
|
||||
def test_sympify():
|
||||
from sympy.abc import x, y, z, t
|
||||
arr = MutableDenseNDimArray([[x, y], [1, z*t]])
|
||||
arr_other = sympify(arr)
|
||||
assert arr_other.shape == (2, 2)
|
||||
assert arr_other == arr
|
||||
|
||||
|
||||
def test_reshape():
|
||||
array = MutableDenseNDimArray(range(50), 50)
|
||||
assert array.shape == (50,)
|
||||
assert array.rank() == 1
|
||||
|
||||
array = array.reshape(5, 5, 2)
|
||||
assert array.shape == (5, 5, 2)
|
||||
assert array.rank() == 3
|
||||
assert len(array) == 50
|
||||
|
||||
|
||||
def test_iterator():
|
||||
array = MutableDenseNDimArray(range(4), (2, 2))
|
||||
assert array[0] == MutableDenseNDimArray([0, 1])
|
||||
assert array[1] == MutableDenseNDimArray([2, 3])
|
||||
|
||||
array = array.reshape(4)
|
||||
j = 0
|
||||
for i in array:
|
||||
assert i == j
|
||||
j += 1
|
||||
|
||||
|
||||
def test_getitem():
|
||||
for ArrayType in [MutableDenseNDimArray, MutableSparseNDimArray]:
|
||||
array = ArrayType(range(24)).reshape(2, 3, 4)
|
||||
assert array.tolist() == [[[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]], [[12, 13, 14, 15], [16, 17, 18, 19], [20, 21, 22, 23]]]
|
||||
assert array[0] == ArrayType([[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]])
|
||||
assert array[0, 0] == ArrayType([0, 1, 2, 3])
|
||||
value = 0
|
||||
for i in range(2):
|
||||
for j in range(3):
|
||||
for k in range(4):
|
||||
assert array[i, j, k] == value
|
||||
value += 1
|
||||
|
||||
raises(ValueError, lambda: array[3, 4, 5])
|
||||
raises(ValueError, lambda: array[3, 4, 5, 6])
|
||||
raises(ValueError, lambda: array[3, 4, 5, 3:4])
|
||||
|
||||
|
||||
def test_sparse():
|
||||
sparse_array = MutableSparseNDimArray([0, 0, 0, 1], (2, 2))
|
||||
assert len(sparse_array) == 2 * 2
|
||||
# dictionary where all data is, only non-zero entries are actually stored:
|
||||
assert len(sparse_array._sparse_array) == 1
|
||||
|
||||
assert sparse_array.tolist() == [[0, 0], [0, 1]]
|
||||
|
||||
for i, j in zip(sparse_array, [[0, 0], [0, 1]]):
|
||||
assert i == MutableSparseNDimArray(j)
|
||||
|
||||
sparse_array[0, 0] = 123
|
||||
assert len(sparse_array._sparse_array) == 2
|
||||
assert sparse_array[0, 0] == 123
|
||||
assert sparse_array/0 == MutableSparseNDimArray([[S.ComplexInfinity, S.NaN], [S.NaN, S.ComplexInfinity]], (2, 2))
|
||||
|
||||
# when element in sparse array become zero it will disappear from
|
||||
# dictionary
|
||||
sparse_array[0, 0] = 0
|
||||
assert len(sparse_array._sparse_array) == 1
|
||||
sparse_array[1, 1] = 0
|
||||
assert len(sparse_array._sparse_array) == 0
|
||||
assert sparse_array[0, 0] == 0
|
||||
|
||||
# test for large scale sparse array
|
||||
# equality test
|
||||
a = MutableSparseNDimArray.zeros(100000, 200000)
|
||||
b = MutableSparseNDimArray.zeros(100000, 200000)
|
||||
assert a == b
|
||||
a[1, 1] = 1
|
||||
b[1, 1] = 2
|
||||
assert a != b
|
||||
|
||||
# __mul__ and __rmul__
|
||||
assert a * 3 == MutableSparseNDimArray({200001: 3}, (100000, 200000))
|
||||
assert 3 * a == MutableSparseNDimArray({200001: 3}, (100000, 200000))
|
||||
assert a * 0 == MutableSparseNDimArray({}, (100000, 200000))
|
||||
assert 0 * a == MutableSparseNDimArray({}, (100000, 200000))
|
||||
|
||||
# __truediv__
|
||||
assert a/3 == MutableSparseNDimArray({200001: Rational(1, 3)}, (100000, 200000))
|
||||
|
||||
# __neg__
|
||||
assert -a == MutableSparseNDimArray({200001: -1}, (100000, 200000))
|
||||
|
||||
|
||||
def test_calculation():
|
||||
|
||||
a = MutableDenseNDimArray([1]*9, (3, 3))
|
||||
b = MutableDenseNDimArray([9]*9, (3, 3))
|
||||
|
||||
c = a + b
|
||||
for i in c:
|
||||
assert i == MutableDenseNDimArray([10, 10, 10])
|
||||
|
||||
assert c == MutableDenseNDimArray([10]*9, (3, 3))
|
||||
assert c == MutableSparseNDimArray([10]*9, (3, 3))
|
||||
|
||||
c = b - a
|
||||
for i in c:
|
||||
assert i == MutableSparseNDimArray([8, 8, 8])
|
||||
|
||||
assert c == MutableDenseNDimArray([8]*9, (3, 3))
|
||||
assert c == MutableSparseNDimArray([8]*9, (3, 3))
|
||||
|
||||
|
||||
def test_ndim_array_converting():
|
||||
dense_array = MutableDenseNDimArray([1, 2, 3, 4], (2, 2))
|
||||
alist = dense_array.tolist()
|
||||
|
||||
assert alist == [[1, 2], [3, 4]]
|
||||
|
||||
matrix = dense_array.tomatrix()
|
||||
assert (isinstance(matrix, Matrix))
|
||||
|
||||
for i in range(len(dense_array)):
|
||||
assert dense_array[dense_array._get_tuple_index(i)] == matrix[i]
|
||||
assert matrix.shape == dense_array.shape
|
||||
|
||||
assert MutableDenseNDimArray(matrix) == dense_array
|
||||
assert MutableDenseNDimArray(matrix.as_immutable()) == dense_array
|
||||
assert MutableDenseNDimArray(matrix.as_mutable()) == dense_array
|
||||
|
||||
sparse_array = MutableSparseNDimArray([1, 2, 3, 4], (2, 2))
|
||||
alist = sparse_array.tolist()
|
||||
|
||||
assert alist == [[1, 2], [3, 4]]
|
||||
|
||||
matrix = sparse_array.tomatrix()
|
||||
assert(isinstance(matrix, SparseMatrix))
|
||||
|
||||
for i in range(len(sparse_array)):
|
||||
assert sparse_array[sparse_array._get_tuple_index(i)] == matrix[i]
|
||||
assert matrix.shape == sparse_array.shape
|
||||
|
||||
assert MutableSparseNDimArray(matrix) == sparse_array
|
||||
assert MutableSparseNDimArray(matrix.as_immutable()) == sparse_array
|
||||
assert MutableSparseNDimArray(matrix.as_mutable()) == sparse_array
|
||||
|
||||
|
||||
def test_converting_functions():
|
||||
arr_list = [1, 2, 3, 4]
|
||||
arr_matrix = Matrix(((1, 2), (3, 4)))
|
||||
|
||||
# list
|
||||
arr_ndim_array = MutableDenseNDimArray(arr_list, (2, 2))
|
||||
assert (isinstance(arr_ndim_array, MutableDenseNDimArray))
|
||||
assert arr_matrix.tolist() == arr_ndim_array.tolist()
|
||||
|
||||
# Matrix
|
||||
arr_ndim_array = MutableDenseNDimArray(arr_matrix)
|
||||
assert (isinstance(arr_ndim_array, MutableDenseNDimArray))
|
||||
assert arr_matrix.tolist() == arr_ndim_array.tolist()
|
||||
assert arr_matrix.shape == arr_ndim_array.shape
|
||||
|
||||
|
||||
def test_equality():
|
||||
first_list = [1, 2, 3, 4]
|
||||
second_list = [1, 2, 3, 4]
|
||||
third_list = [4, 3, 2, 1]
|
||||
assert first_list == second_list
|
||||
assert first_list != third_list
|
||||
|
||||
first_ndim_array = MutableDenseNDimArray(first_list, (2, 2))
|
||||
second_ndim_array = MutableDenseNDimArray(second_list, (2, 2))
|
||||
third_ndim_array = MutableDenseNDimArray(third_list, (2, 2))
|
||||
fourth_ndim_array = MutableDenseNDimArray(first_list, (2, 2))
|
||||
|
||||
assert first_ndim_array == second_ndim_array
|
||||
second_ndim_array[0, 0] = 0
|
||||
assert first_ndim_array != second_ndim_array
|
||||
assert first_ndim_array != third_ndim_array
|
||||
assert first_ndim_array == fourth_ndim_array
|
||||
|
||||
|
||||
def test_arithmetic():
|
||||
a = MutableDenseNDimArray([3 for i in range(9)], (3, 3))
|
||||
b = MutableDenseNDimArray([7 for i in range(9)], (3, 3))
|
||||
|
||||
c1 = a + b
|
||||
c2 = b + a
|
||||
assert c1 == c2
|
||||
|
||||
d1 = a - b
|
||||
d2 = b - a
|
||||
assert d1 == d2 * (-1)
|
||||
|
||||
e1 = a * 5
|
||||
e2 = 5 * a
|
||||
e3 = copy(a)
|
||||
e3 *= 5
|
||||
assert e1 == e2 == e3
|
||||
|
||||
f1 = a / 5
|
||||
f2 = copy(a)
|
||||
f2 /= 5
|
||||
assert f1 == f2
|
||||
assert f1[0, 0] == f1[0, 1] == f1[0, 2] == f1[1, 0] == f1[1, 1] == \
|
||||
f1[1, 2] == f1[2, 0] == f1[2, 1] == f1[2, 2] == Rational(3, 5)
|
||||
|
||||
assert type(a) == type(b) == type(c1) == type(c2) == type(d1) == type(d2) \
|
||||
== type(e1) == type(e2) == type(e3) == type(f1)
|
||||
|
||||
z0 = -a
|
||||
assert z0 == MutableDenseNDimArray([-3 for i in range(9)], (3, 3))
|
||||
|
||||
|
||||
def test_higher_dimenions():
|
||||
m3 = MutableDenseNDimArray(range(10, 34), (2, 3, 4))
|
||||
|
||||
assert m3.tolist() == [[[10, 11, 12, 13],
|
||||
[14, 15, 16, 17],
|
||||
[18, 19, 20, 21]],
|
||||
|
||||
[[22, 23, 24, 25],
|
||||
[26, 27, 28, 29],
|
||||
[30, 31, 32, 33]]]
|
||||
|
||||
assert m3._get_tuple_index(0) == (0, 0, 0)
|
||||
assert m3._get_tuple_index(1) == (0, 0, 1)
|
||||
assert m3._get_tuple_index(4) == (0, 1, 0)
|
||||
assert m3._get_tuple_index(12) == (1, 0, 0)
|
||||
|
||||
assert str(m3) == '[[[10, 11, 12, 13], [14, 15, 16, 17], [18, 19, 20, 21]], [[22, 23, 24, 25], [26, 27, 28, 29], [30, 31, 32, 33]]]'
|
||||
|
||||
m3_rebuilt = MutableDenseNDimArray([[[10, 11, 12, 13], [14, 15, 16, 17], [18, 19, 20, 21]], [[22, 23, 24, 25], [26, 27, 28, 29], [30, 31, 32, 33]]])
|
||||
assert m3 == m3_rebuilt
|
||||
|
||||
m3_other = MutableDenseNDimArray([[[10, 11, 12, 13], [14, 15, 16, 17], [18, 19, 20, 21]], [[22, 23, 24, 25], [26, 27, 28, 29], [30, 31, 32, 33]]], (2, 3, 4))
|
||||
|
||||
assert m3 == m3_other
|
||||
|
||||
|
||||
def test_slices():
|
||||
md = MutableDenseNDimArray(range(10, 34), (2, 3, 4))
|
||||
|
||||
assert md[:] == MutableDenseNDimArray(range(10, 34), (2, 3, 4))
|
||||
assert md[:, :, 0].tomatrix() == Matrix([[10, 14, 18], [22, 26, 30]])
|
||||
assert md[0, 1:2, :].tomatrix() == Matrix([[14, 15, 16, 17]])
|
||||
assert md[0, 1:3, :].tomatrix() == Matrix([[14, 15, 16, 17], [18, 19, 20, 21]])
|
||||
assert md[:, :, :] == md
|
||||
|
||||
sd = MutableSparseNDimArray(range(10, 34), (2, 3, 4))
|
||||
assert sd == MutableSparseNDimArray(md)
|
||||
|
||||
assert sd[:] == MutableSparseNDimArray(range(10, 34), (2, 3, 4))
|
||||
assert sd[:, :, 0].tomatrix() == Matrix([[10, 14, 18], [22, 26, 30]])
|
||||
assert sd[0, 1:2, :].tomatrix() == Matrix([[14, 15, 16, 17]])
|
||||
assert sd[0, 1:3, :].tomatrix() == Matrix([[14, 15, 16, 17], [18, 19, 20, 21]])
|
||||
assert sd[:, :, :] == sd
|
||||
|
||||
|
||||
def test_slices_assign():
|
||||
a = MutableDenseNDimArray(range(12), shape=(4, 3))
|
||||
b = MutableSparseNDimArray(range(12), shape=(4, 3))
|
||||
|
||||
for i in [a, b]:
|
||||
assert i.tolist() == [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11]]
|
||||
i[0, :] = [2, 2, 2]
|
||||
assert i.tolist() == [[2, 2, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11]]
|
||||
i[0, 1:] = [8, 8]
|
||||
assert i.tolist() == [[2, 8, 8], [3, 4, 5], [6, 7, 8], [9, 10, 11]]
|
||||
i[1:3, 1] = [20, 44]
|
||||
assert i.tolist() == [[2, 8, 8], [3, 20, 5], [6, 44, 8], [9, 10, 11]]
|
||||
|
||||
|
||||
def test_diff():
|
||||
from sympy.abc import x, y, z
|
||||
md = MutableDenseNDimArray([[x, y], [x*z, x*y*z]])
|
||||
assert md.diff(x) == MutableDenseNDimArray([[1, 0], [z, y*z]])
|
||||
assert diff(md, x) == MutableDenseNDimArray([[1, 0], [z, y*z]])
|
||||
|
||||
sd = MutableSparseNDimArray(md)
|
||||
assert sd == MutableSparseNDimArray([x, y, x*z, x*y*z], (2, 2))
|
||||
assert sd.diff(x) == MutableSparseNDimArray([[1, 0], [z, y*z]])
|
||||
assert diff(sd, x) == MutableSparseNDimArray([[1, 0], [z, y*z]])
|
||||
@@ -0,0 +1,73 @@
|
||||
from sympy.testing.pytest import raises
|
||||
from sympy.functions.elementary.trigonometric import sin, cos
|
||||
from sympy.matrices.dense import Matrix
|
||||
from sympy.simplify import simplify
|
||||
from sympy.tensor.array import Array
|
||||
from sympy.tensor.array.dense_ndim_array import (
|
||||
ImmutableDenseNDimArray, MutableDenseNDimArray)
|
||||
from sympy.tensor.array.sparse_ndim_array import (
|
||||
ImmutableSparseNDimArray, MutableSparseNDimArray)
|
||||
|
||||
from sympy.abc import x, y
|
||||
|
||||
mutable_array_types = [
|
||||
MutableDenseNDimArray,
|
||||
MutableSparseNDimArray
|
||||
]
|
||||
|
||||
array_types = [
|
||||
ImmutableDenseNDimArray,
|
||||
ImmutableSparseNDimArray,
|
||||
MutableDenseNDimArray,
|
||||
MutableSparseNDimArray
|
||||
]
|
||||
|
||||
|
||||
def test_array_negative_indices():
|
||||
for ArrayType in array_types:
|
||||
test_array = ArrayType([[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]])
|
||||
assert test_array[:, -1] == Array([5, 10])
|
||||
assert test_array[:, -2] == Array([4, 9])
|
||||
assert test_array[:, -3] == Array([3, 8])
|
||||
assert test_array[:, -4] == Array([2, 7])
|
||||
assert test_array[:, -5] == Array([1, 6])
|
||||
assert test_array[:, 0] == Array([1, 6])
|
||||
assert test_array[:, 1] == Array([2, 7])
|
||||
assert test_array[:, 2] == Array([3, 8])
|
||||
assert test_array[:, 3] == Array([4, 9])
|
||||
assert test_array[:, 4] == Array([5, 10])
|
||||
|
||||
raises(ValueError, lambda: test_array[:, -6])
|
||||
raises(ValueError, lambda: test_array[-3, :])
|
||||
|
||||
assert test_array[-1, -1] == 10
|
||||
|
||||
|
||||
def test_issue_18361():
|
||||
A = Array([sin(2 * x) - 2 * sin(x) * cos(x)])
|
||||
B = Array([sin(x)**2 + cos(x)**2, 0])
|
||||
C = Array([(x + x**2)/(x*sin(y)**2 + x*cos(y)**2), 2*sin(x)*cos(x)])
|
||||
assert simplify(A) == Array([0])
|
||||
assert simplify(B) == Array([1, 0])
|
||||
assert simplify(C) == Array([x + 1, sin(2*x)])
|
||||
|
||||
|
||||
def test_issue_20222():
|
||||
A = Array([[1, 2], [3, 4]])
|
||||
B = Matrix([[1,2],[3,4]])
|
||||
raises(TypeError, lambda: A - B)
|
||||
|
||||
|
||||
def test_issue_17851():
|
||||
for array_type in array_types:
|
||||
A = array_type([])
|
||||
assert isinstance(A, array_type)
|
||||
assert A.shape == (0,)
|
||||
assert list(A) == []
|
||||
|
||||
|
||||
def test_issue_and_18715():
|
||||
for array_type in mutable_array_types:
|
||||
A = array_type([0, 1, 2])
|
||||
A[0] += 5
|
||||
assert A[0] == 5
|
||||
@@ -0,0 +1,22 @@
|
||||
from sympy.tensor.array import (ImmutableDenseNDimArray,
|
||||
ImmutableSparseNDimArray, MutableDenseNDimArray, MutableSparseNDimArray)
|
||||
from sympy.abc import x, y, z
|
||||
|
||||
|
||||
def test_NDim_array_conv():
|
||||
MD = MutableDenseNDimArray([x, y, z])
|
||||
MS = MutableSparseNDimArray([x, y, z])
|
||||
ID = ImmutableDenseNDimArray([x, y, z])
|
||||
IS = ImmutableSparseNDimArray([x, y, z])
|
||||
|
||||
assert MD.as_immutable() == ID
|
||||
assert MD.as_mutable() == MD
|
||||
|
||||
assert MS.as_immutable() == IS
|
||||
assert MS.as_mutable() == MS
|
||||
|
||||
assert ID.as_immutable() == ID
|
||||
assert ID.as_mutable() == MD
|
||||
|
||||
assert IS.as_immutable() == IS
|
||||
assert IS.as_mutable() == MS
|
||||
Reference in New Issue
Block a user