Coverage for /usr/lib/python3/dist-packages/sympy/tensor/array/array_comprehension.py: 31%
137 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
1import functools, itertools
2from sympy.core.sympify import _sympify, sympify
3from sympy.core.expr import Expr
4from sympy.core import Basic, Tuple
5from sympy.tensor.array import ImmutableDenseNDimArray
6from sympy.core.symbol import Symbol
7from sympy.core.numbers import Integer
10class ArrayComprehension(Basic):
11 """
12 Generate a list comprehension.
14 Explanation
15 ===========
17 If there is a symbolic dimension, for example, say [i for i in range(1, N)] where
18 N is a Symbol, then the expression will not be expanded to an array. Otherwise,
19 calling the doit() function will launch the expansion.
21 Examples
22 ========
24 >>> from sympy.tensor.array import ArrayComprehension
25 >>> from sympy import symbols
26 >>> i, j, k = symbols('i j k')
27 >>> a = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, 3))
28 >>> a
29 ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, 3))
30 >>> a.doit()
31 [[11, 12, 13], [21, 22, 23], [31, 32, 33], [41, 42, 43]]
32 >>> b = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, k))
33 >>> b.doit()
34 ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, k))
35 """
36 def __new__(cls, function, *symbols, **assumptions):
37 if any(len(l) != 3 or None for l in symbols):
38 raise ValueError('ArrayComprehension requires values lower and upper bound'
39 ' for the expression')
40 arglist = [sympify(function)]
41 arglist.extend(cls._check_limits_validity(function, symbols))
42 obj = Basic.__new__(cls, *arglist, **assumptions)
43 obj._limits = obj._args[1:]
44 obj._shape = cls._calculate_shape_from_limits(obj._limits)
45 obj._rank = len(obj._shape)
46 obj._loop_size = cls._calculate_loop_size(obj._shape)
47 return obj
49 @property
50 def function(self):
51 """The function applied across limits.
53 Examples
54 ========
56 >>> from sympy.tensor.array import ArrayComprehension
57 >>> from sympy import symbols
58 >>> i, j = symbols('i j')
59 >>> a = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, 3))
60 >>> a.function
61 10*i + j
62 """
63 return self._args[0]
65 @property
66 def limits(self):
67 """
68 The list of limits that will be applied while expanding the array.
70 Examples
71 ========
73 >>> from sympy.tensor.array import ArrayComprehension
74 >>> from sympy import symbols
75 >>> i, j = symbols('i j')
76 >>> a = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, 3))
77 >>> a.limits
78 ((i, 1, 4), (j, 1, 3))
79 """
80 return self._limits
82 @property
83 def free_symbols(self):
84 """
85 The set of the free_symbols in the array.
86 Variables appeared in the bounds are supposed to be excluded
87 from the free symbol set.
89 Examples
90 ========
92 >>> from sympy.tensor.array import ArrayComprehension
93 >>> from sympy import symbols
94 >>> i, j, k = symbols('i j k')
95 >>> a = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, 3))
96 >>> a.free_symbols
97 set()
98 >>> b = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, k+3))
99 >>> b.free_symbols
100 {k}
101 """
102 expr_free_sym = self.function.free_symbols
103 for var, inf, sup in self._limits:
104 expr_free_sym.discard(var)
105 curr_free_syms = inf.free_symbols.union(sup.free_symbols)
106 expr_free_sym = expr_free_sym.union(curr_free_syms)
107 return expr_free_sym
109 @property
110 def variables(self):
111 """The tuples of the variables in the limits.
113 Examples
114 ========
116 >>> from sympy.tensor.array import ArrayComprehension
117 >>> from sympy import symbols
118 >>> i, j, k = symbols('i j k')
119 >>> a = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, 3))
120 >>> a.variables
121 [i, j]
122 """
123 return [l[0] for l in self._limits]
125 @property
126 def bound_symbols(self):
127 """The list of dummy variables.
129 Note
130 ====
132 Note that all variables are dummy variables since a limit without
133 lower bound or upper bound is not accepted.
134 """
135 return [l[0] for l in self._limits if len(l) != 1]
137 @property
138 def shape(self):
139 """
140 The shape of the expanded array, which may have symbols.
142 Note
143 ====
145 Both the lower and the upper bounds are included while
146 calculating the shape.
148 Examples
149 ========
151 >>> from sympy.tensor.array import ArrayComprehension
152 >>> from sympy import symbols
153 >>> i, j, k = symbols('i j k')
154 >>> a = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, 3))
155 >>> a.shape
156 (4, 3)
157 >>> b = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, k+3))
158 >>> b.shape
159 (4, k + 3)
160 """
161 return self._shape
163 @property
164 def is_shape_numeric(self):
165 """
166 Test if the array is shape-numeric which means there is no symbolic
167 dimension.
169 Examples
170 ========
172 >>> from sympy.tensor.array import ArrayComprehension
173 >>> from sympy import symbols
174 >>> i, j, k = symbols('i j k')
175 >>> a = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, 3))
176 >>> a.is_shape_numeric
177 True
178 >>> b = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, k+3))
179 >>> b.is_shape_numeric
180 False
181 """
182 for _, inf, sup in self._limits:
183 if Basic(inf, sup).atoms(Symbol):
184 return False
185 return True
187 def rank(self):
188 """The rank of the expanded array.
190 Examples
191 ========
193 >>> from sympy.tensor.array import ArrayComprehension
194 >>> from sympy import symbols
195 >>> i, j, k = symbols('i j k')
196 >>> a = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, 3))
197 >>> a.rank()
198 2
199 """
200 return self._rank
202 def __len__(self):
203 """
204 The length of the expanded array which means the number
205 of elements in the array.
207 Raises
208 ======
210 ValueError : When the length of the array is symbolic
212 Examples
213 ========
215 >>> from sympy.tensor.array import ArrayComprehension
216 >>> from sympy import symbols
217 >>> i, j = symbols('i j')
218 >>> a = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, 3))
219 >>> len(a)
220 12
221 """
222 if self._loop_size.free_symbols:
223 raise ValueError('Symbolic length is not supported')
224 return self._loop_size
226 @classmethod
227 def _check_limits_validity(cls, function, limits):
228 #limits = sympify(limits)
229 new_limits = []
230 for var, inf, sup in limits:
231 var = _sympify(var)
232 inf = _sympify(inf)
233 #since this is stored as an argument, it should be
234 #a Tuple
235 if isinstance(sup, list):
236 sup = Tuple(*sup)
237 else:
238 sup = _sympify(sup)
239 new_limits.append(Tuple(var, inf, sup))
240 if any((not isinstance(i, Expr)) or i.atoms(Symbol, Integer) != i.atoms()
241 for i in [inf, sup]):
242 raise TypeError('Bounds should be an Expression(combination of Integer and Symbol)')
243 if (inf > sup) == True:
244 raise ValueError('Lower bound should be inferior to upper bound')
245 if var in inf.free_symbols or var in sup.free_symbols:
246 raise ValueError('Variable should not be part of its bounds')
247 return new_limits
249 @classmethod
250 def _calculate_shape_from_limits(cls, limits):
251 return tuple([sup - inf + 1 for _, inf, sup in limits])
253 @classmethod
254 def _calculate_loop_size(cls, shape):
255 if not shape:
256 return 0
257 loop_size = 1
258 for l in shape:
259 loop_size = loop_size * l
261 return loop_size
263 def doit(self, **hints):
264 if not self.is_shape_numeric:
265 return self
267 return self._expand_array()
269 def _expand_array(self):
270 res = []
271 for values in itertools.product(*[range(inf, sup+1)
272 for var, inf, sup
273 in self._limits]):
274 res.append(self._get_element(values))
276 return ImmutableDenseNDimArray(res, self.shape)
278 def _get_element(self, values):
279 temp = self.function
280 for var, val in zip(self.variables, values):
281 temp = temp.subs(var, val)
282 return temp
284 def tolist(self):
285 """Transform the expanded array to a list.
287 Raises
288 ======
290 ValueError : When there is a symbolic dimension
292 Examples
293 ========
295 >>> from sympy.tensor.array import ArrayComprehension
296 >>> from sympy import symbols
297 >>> i, j = symbols('i j')
298 >>> a = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, 3))
299 >>> a.tolist()
300 [[11, 12, 13], [21, 22, 23], [31, 32, 33], [41, 42, 43]]
301 """
302 if self.is_shape_numeric:
303 return self._expand_array().tolist()
305 raise ValueError("A symbolic array cannot be expanded to a list")
307 def tomatrix(self):
308 """Transform the expanded array to a matrix.
310 Raises
311 ======
313 ValueError : When there is a symbolic dimension
314 ValueError : When the rank of the expanded array is not equal to 2
316 Examples
317 ========
319 >>> from sympy.tensor.array import ArrayComprehension
320 >>> from sympy import symbols
321 >>> i, j = symbols('i j')
322 >>> a = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, 3))
323 >>> a.tomatrix()
324 Matrix([
325 [11, 12, 13],
326 [21, 22, 23],
327 [31, 32, 33],
328 [41, 42, 43]])
329 """
330 from sympy.matrices import Matrix
332 if not self.is_shape_numeric:
333 raise ValueError("A symbolic array cannot be expanded to a matrix")
334 if self._rank != 2:
335 raise ValueError('Dimensions must be of size of 2')
337 return Matrix(self._expand_array().tomatrix())
340def isLambda(v):
341 LAMBDA = lambda: 0
342 return isinstance(v, type(LAMBDA)) and v.__name__ == LAMBDA.__name__
344class ArrayComprehensionMap(ArrayComprehension):
345 '''
346 A subclass of ArrayComprehension dedicated to map external function lambda.
348 Notes
349 =====
351 Only the lambda function is considered.
352 At most one argument in lambda function is accepted in order to avoid ambiguity
353 in value assignment.
355 Examples
356 ========
358 >>> from sympy.tensor.array import ArrayComprehensionMap
359 >>> from sympy import symbols
360 >>> i, j, k = symbols('i j k')
361 >>> a = ArrayComprehensionMap(lambda: 1, (i, 1, 4))
362 >>> a.doit()
363 [1, 1, 1, 1]
364 >>> b = ArrayComprehensionMap(lambda a: a+1, (j, 1, 4))
365 >>> b.doit()
366 [2, 3, 4, 5]
368 '''
369 def __new__(cls, function, *symbols, **assumptions):
370 if any(len(l) != 3 or None for l in symbols):
371 raise ValueError('ArrayComprehension requires values lower and upper bound'
372 ' for the expression')
374 if not isLambda(function):
375 raise ValueError('Data type not supported')
377 arglist = cls._check_limits_validity(function, symbols)
378 obj = Basic.__new__(cls, *arglist, **assumptions)
379 obj._limits = obj._args
380 obj._shape = cls._calculate_shape_from_limits(obj._limits)
381 obj._rank = len(obj._shape)
382 obj._loop_size = cls._calculate_loop_size(obj._shape)
383 obj._lambda = function
384 return obj
386 @property
387 def func(self):
388 class _(ArrayComprehensionMap):
389 def __new__(cls, *args, **kwargs):
390 return ArrayComprehensionMap(self._lambda, *args, **kwargs)
391 return _
393 def _get_element(self, values):
394 temp = self._lambda
395 if self._lambda.__code__.co_argcount == 0:
396 temp = temp()
397 elif self._lambda.__code__.co_argcount == 1:
398 temp = temp(functools.reduce(lambda a, b: a*b, values))
399 return temp