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

400 lines
12 KiB
Python
Raw Permalink Normal View History

2024-05-03 04:18:51 +03:00
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