ai-content-maker/.venv/Lib/site-packages/sympy/tensor/array/ndim_array.py

601 lines
19 KiB
Python

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 i, sh in enumerate(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.matrices 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.matrices 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, v in new_dict.items():
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[()])
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.matrices 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.matrices 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.matrices 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")