123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600 |
- 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")
|