Coverage for /usr/lib/python3/dist-packages/sympy/functions/special/tensor_functions.py: 37%
114 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
1from math import prod
3from sympy.core import S, Integer
4from sympy.core.function import Function
5from sympy.core.logic import fuzzy_not
6from sympy.core.relational import Ne
7from sympy.core.sorting import default_sort_key
8from sympy.external.gmpy import SYMPY_INTS
9from sympy.functions.combinatorial.factorials import factorial
10from sympy.functions.elementary.piecewise import Piecewise
11from sympy.utilities.iterables import has_dups
13###############################################################################
14###################### Kronecker Delta, Levi-Civita etc. ######################
15###############################################################################
18def Eijk(*args, **kwargs):
19 """
20 Represent the Levi-Civita symbol.
22 This is a compatibility wrapper to ``LeviCivita()``.
24 See Also
25 ========
27 LeviCivita
29 """
30 return LeviCivita(*args, **kwargs)
33def eval_levicivita(*args):
34 """Evaluate Levi-Civita symbol."""
35 n = len(args)
36 return prod(
37 prod(args[j] - args[i] for j in range(i + 1, n))
38 / factorial(i) for i in range(n))
39 # converting factorial(i) to int is slightly faster
42class LeviCivita(Function):
43 """
44 Represent the Levi-Civita symbol.
46 Explanation
47 ===========
49 For even permutations of indices it returns 1, for odd permutations -1, and
50 for everything else (a repeated index) it returns 0.
52 Thus it represents an alternating pseudotensor.
54 Examples
55 ========
57 >>> from sympy import LeviCivita
58 >>> from sympy.abc import i, j, k
59 >>> LeviCivita(1, 2, 3)
60 1
61 >>> LeviCivita(1, 3, 2)
62 -1
63 >>> LeviCivita(1, 2, 2)
64 0
65 >>> LeviCivita(i, j, k)
66 LeviCivita(i, j, k)
67 >>> LeviCivita(i, j, i)
68 0
70 See Also
71 ========
73 Eijk
75 """
77 is_integer = True
79 @classmethod
80 def eval(cls, *args):
81 if all(isinstance(a, (SYMPY_INTS, Integer)) for a in args):
82 return eval_levicivita(*args)
83 if has_dups(args):
84 return S.Zero
86 def doit(self, **hints):
87 return eval_levicivita(*self.args)
90class KroneckerDelta(Function):
91 """
92 The discrete, or Kronecker, delta function.
94 Explanation
95 ===========
97 A function that takes in two integers $i$ and $j$. It returns $0$ if $i$
98 and $j$ are not equal, or it returns $1$ if $i$ and $j$ are equal.
100 Examples
101 ========
103 An example with integer indices:
105 >>> from sympy import KroneckerDelta
106 >>> KroneckerDelta(1, 2)
107 0
108 >>> KroneckerDelta(3, 3)
109 1
111 Symbolic indices:
113 >>> from sympy.abc import i, j, k
114 >>> KroneckerDelta(i, j)
115 KroneckerDelta(i, j)
116 >>> KroneckerDelta(i, i)
117 1
118 >>> KroneckerDelta(i, i + 1)
119 0
120 >>> KroneckerDelta(i, i + 1 + k)
121 KroneckerDelta(i, i + k + 1)
123 Parameters
124 ==========
126 i : Number, Symbol
127 The first index of the delta function.
128 j : Number, Symbol
129 The second index of the delta function.
131 See Also
132 ========
134 eval
135 DiracDelta
137 References
138 ==========
140 .. [1] https://en.wikipedia.org/wiki/Kronecker_delta
142 """
144 is_integer = True
146 @classmethod
147 def eval(cls, i, j, delta_range=None):
148 """
149 Evaluates the discrete delta function.
151 Examples
152 ========
154 >>> from sympy import KroneckerDelta
155 >>> from sympy.abc import i, j, k
157 >>> KroneckerDelta(i, j)
158 KroneckerDelta(i, j)
159 >>> KroneckerDelta(i, i)
160 1
161 >>> KroneckerDelta(i, i + 1)
162 0
163 >>> KroneckerDelta(i, i + 1 + k)
164 KroneckerDelta(i, i + k + 1)
166 # indirect doctest
168 """
170 if delta_range is not None:
171 dinf, dsup = delta_range
172 if (dinf - i > 0) == True:
173 return S.Zero
174 if (dinf - j > 0) == True:
175 return S.Zero
176 if (dsup - i < 0) == True:
177 return S.Zero
178 if (dsup - j < 0) == True:
179 return S.Zero
181 diff = i - j
182 if diff.is_zero:
183 return S.One
184 elif fuzzy_not(diff.is_zero):
185 return S.Zero
187 if i.assumptions0.get("below_fermi") and \
188 j.assumptions0.get("above_fermi"):
189 return S.Zero
190 if j.assumptions0.get("below_fermi") and \
191 i.assumptions0.get("above_fermi"):
192 return S.Zero
193 # to make KroneckerDelta canonical
194 # following lines will check if inputs are in order
195 # if not, will return KroneckerDelta with correct order
196 if i != min(i, j, key=default_sort_key):
197 if delta_range:
198 return cls(j, i, delta_range)
199 else:
200 return cls(j, i)
202 @property
203 def delta_range(self):
204 if len(self.args) > 2:
205 return self.args[2]
207 def _eval_power(self, expt):
208 if expt.is_positive:
209 return self
210 if expt.is_negative and expt is not S.NegativeOne:
211 return 1/self
213 @property
214 def is_above_fermi(self):
215 """
216 True if Delta can be non-zero above fermi.
218 Examples
219 ========
221 >>> from sympy import KroneckerDelta, Symbol
222 >>> a = Symbol('a', above_fermi=True)
223 >>> i = Symbol('i', below_fermi=True)
224 >>> p = Symbol('p')
225 >>> q = Symbol('q')
226 >>> KroneckerDelta(p, a).is_above_fermi
227 True
228 >>> KroneckerDelta(p, i).is_above_fermi
229 False
230 >>> KroneckerDelta(p, q).is_above_fermi
231 True
233 See Also
234 ========
236 is_below_fermi, is_only_below_fermi, is_only_above_fermi
238 """
239 if self.args[0].assumptions0.get("below_fermi"):
240 return False
241 if self.args[1].assumptions0.get("below_fermi"):
242 return False
243 return True
245 @property
246 def is_below_fermi(self):
247 """
248 True if Delta can be non-zero below fermi.
250 Examples
251 ========
253 >>> from sympy import KroneckerDelta, Symbol
254 >>> a = Symbol('a', above_fermi=True)
255 >>> i = Symbol('i', below_fermi=True)
256 >>> p = Symbol('p')
257 >>> q = Symbol('q')
258 >>> KroneckerDelta(p, a).is_below_fermi
259 False
260 >>> KroneckerDelta(p, i).is_below_fermi
261 True
262 >>> KroneckerDelta(p, q).is_below_fermi
263 True
265 See Also
266 ========
268 is_above_fermi, is_only_above_fermi, is_only_below_fermi
270 """
271 if self.args[0].assumptions0.get("above_fermi"):
272 return False
273 if self.args[1].assumptions0.get("above_fermi"):
274 return False
275 return True
277 @property
278 def is_only_above_fermi(self):
279 """
280 True if Delta is restricted to above fermi.
282 Examples
283 ========
285 >>> from sympy import KroneckerDelta, Symbol
286 >>> a = Symbol('a', above_fermi=True)
287 >>> i = Symbol('i', below_fermi=True)
288 >>> p = Symbol('p')
289 >>> q = Symbol('q')
290 >>> KroneckerDelta(p, a).is_only_above_fermi
291 True
292 >>> KroneckerDelta(p, q).is_only_above_fermi
293 False
294 >>> KroneckerDelta(p, i).is_only_above_fermi
295 False
297 See Also
298 ========
300 is_above_fermi, is_below_fermi, is_only_below_fermi
302 """
303 return ( self.args[0].assumptions0.get("above_fermi")
304 or
305 self.args[1].assumptions0.get("above_fermi")
306 ) or False
308 @property
309 def is_only_below_fermi(self):
310 """
311 True if Delta is restricted to below fermi.
313 Examples
314 ========
316 >>> from sympy import KroneckerDelta, Symbol
317 >>> a = Symbol('a', above_fermi=True)
318 >>> i = Symbol('i', below_fermi=True)
319 >>> p = Symbol('p')
320 >>> q = Symbol('q')
321 >>> KroneckerDelta(p, i).is_only_below_fermi
322 True
323 >>> KroneckerDelta(p, q).is_only_below_fermi
324 False
325 >>> KroneckerDelta(p, a).is_only_below_fermi
326 False
328 See Also
329 ========
331 is_above_fermi, is_below_fermi, is_only_above_fermi
333 """
334 return ( self.args[0].assumptions0.get("below_fermi")
335 or
336 self.args[1].assumptions0.get("below_fermi")
337 ) or False
339 @property
340 def indices_contain_equal_information(self):
341 """
342 Returns True if indices are either both above or below fermi.
344 Examples
345 ========
347 >>> from sympy import KroneckerDelta, Symbol
348 >>> a = Symbol('a', above_fermi=True)
349 >>> i = Symbol('i', below_fermi=True)
350 >>> p = Symbol('p')
351 >>> q = Symbol('q')
352 >>> KroneckerDelta(p, q).indices_contain_equal_information
353 True
354 >>> KroneckerDelta(p, q+1).indices_contain_equal_information
355 True
356 >>> KroneckerDelta(i, p).indices_contain_equal_information
357 False
359 """
360 if (self.args[0].assumptions0.get("below_fermi") and
361 self.args[1].assumptions0.get("below_fermi")):
362 return True
363 if (self.args[0].assumptions0.get("above_fermi")
364 and self.args[1].assumptions0.get("above_fermi")):
365 return True
367 # if both indices are general we are True, else false
368 return self.is_below_fermi and self.is_above_fermi
370 @property
371 def preferred_index(self):
372 """
373 Returns the index which is preferred to keep in the final expression.
375 Explanation
376 ===========
378 The preferred index is the index with more information regarding fermi
379 level. If indices contain the same information, 'a' is preferred before
380 'b'.
382 Examples
383 ========
385 >>> from sympy import KroneckerDelta, Symbol
386 >>> a = Symbol('a', above_fermi=True)
387 >>> i = Symbol('i', below_fermi=True)
388 >>> j = Symbol('j', below_fermi=True)
389 >>> p = Symbol('p')
390 >>> KroneckerDelta(p, i).preferred_index
391 i
392 >>> KroneckerDelta(p, a).preferred_index
393 a
394 >>> KroneckerDelta(i, j).preferred_index
395 i
397 See Also
398 ========
400 killable_index
402 """
403 if self._get_preferred_index():
404 return self.args[1]
405 else:
406 return self.args[0]
408 @property
409 def killable_index(self):
410 """
411 Returns the index which is preferred to substitute in the final
412 expression.
414 Explanation
415 ===========
417 The index to substitute is the index with less information regarding
418 fermi level. If indices contain the same information, 'a' is preferred
419 before 'b'.
421 Examples
422 ========
424 >>> from sympy import KroneckerDelta, Symbol
425 >>> a = Symbol('a', above_fermi=True)
426 >>> i = Symbol('i', below_fermi=True)
427 >>> j = Symbol('j', below_fermi=True)
428 >>> p = Symbol('p')
429 >>> KroneckerDelta(p, i).killable_index
430 p
431 >>> KroneckerDelta(p, a).killable_index
432 p
433 >>> KroneckerDelta(i, j).killable_index
434 j
436 See Also
437 ========
439 preferred_index
441 """
442 if self._get_preferred_index():
443 return self.args[0]
444 else:
445 return self.args[1]
447 def _get_preferred_index(self):
448 """
449 Returns the index which is preferred to keep in the final expression.
451 The preferred index is the index with more information regarding fermi
452 level. If indices contain the same information, index 0 is returned.
454 """
455 if not self.is_above_fermi:
456 if self.args[0].assumptions0.get("below_fermi"):
457 return 0
458 else:
459 return 1
460 elif not self.is_below_fermi:
461 if self.args[0].assumptions0.get("above_fermi"):
462 return 0
463 else:
464 return 1
465 else:
466 return 0
468 @property
469 def indices(self):
470 return self.args[0:2]
472 def _eval_rewrite_as_Piecewise(self, *args, **kwargs):
473 i, j = args
474 return Piecewise((0, Ne(i, j)), (1, True))