123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399 |
- 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
|