475 lines
12 KiB
Python
475 lines
12 KiB
Python
|
from math import prod
|
||
|
|
||
|
from sympy.core import S, Integer
|
||
|
from sympy.core.function import Function
|
||
|
from sympy.core.logic import fuzzy_not
|
||
|
from sympy.core.relational import Ne
|
||
|
from sympy.core.sorting import default_sort_key
|
||
|
from sympy.external.gmpy import SYMPY_INTS
|
||
|
from sympy.functions.combinatorial.factorials import factorial
|
||
|
from sympy.functions.elementary.piecewise import Piecewise
|
||
|
from sympy.utilities.iterables import has_dups
|
||
|
|
||
|
###############################################################################
|
||
|
###################### Kronecker Delta, Levi-Civita etc. ######################
|
||
|
###############################################################################
|
||
|
|
||
|
|
||
|
def Eijk(*args, **kwargs):
|
||
|
"""
|
||
|
Represent the Levi-Civita symbol.
|
||
|
|
||
|
This is a compatibility wrapper to ``LeviCivita()``.
|
||
|
|
||
|
See Also
|
||
|
========
|
||
|
|
||
|
LeviCivita
|
||
|
|
||
|
"""
|
||
|
return LeviCivita(*args, **kwargs)
|
||
|
|
||
|
|
||
|
def eval_levicivita(*args):
|
||
|
"""Evaluate Levi-Civita symbol."""
|
||
|
n = len(args)
|
||
|
return prod(
|
||
|
prod(args[j] - args[i] for j in range(i + 1, n))
|
||
|
/ factorial(i) for i in range(n))
|
||
|
# converting factorial(i) to int is slightly faster
|
||
|
|
||
|
|
||
|
class LeviCivita(Function):
|
||
|
"""
|
||
|
Represent the Levi-Civita symbol.
|
||
|
|
||
|
Explanation
|
||
|
===========
|
||
|
|
||
|
For even permutations of indices it returns 1, for odd permutations -1, and
|
||
|
for everything else (a repeated index) it returns 0.
|
||
|
|
||
|
Thus it represents an alternating pseudotensor.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy import LeviCivita
|
||
|
>>> from sympy.abc import i, j, k
|
||
|
>>> LeviCivita(1, 2, 3)
|
||
|
1
|
||
|
>>> LeviCivita(1, 3, 2)
|
||
|
-1
|
||
|
>>> LeviCivita(1, 2, 2)
|
||
|
0
|
||
|
>>> LeviCivita(i, j, k)
|
||
|
LeviCivita(i, j, k)
|
||
|
>>> LeviCivita(i, j, i)
|
||
|
0
|
||
|
|
||
|
See Also
|
||
|
========
|
||
|
|
||
|
Eijk
|
||
|
|
||
|
"""
|
||
|
|
||
|
is_integer = True
|
||
|
|
||
|
@classmethod
|
||
|
def eval(cls, *args):
|
||
|
if all(isinstance(a, (SYMPY_INTS, Integer)) for a in args):
|
||
|
return eval_levicivita(*args)
|
||
|
if has_dups(args):
|
||
|
return S.Zero
|
||
|
|
||
|
def doit(self, **hints):
|
||
|
return eval_levicivita(*self.args)
|
||
|
|
||
|
|
||
|
class KroneckerDelta(Function):
|
||
|
"""
|
||
|
The discrete, or Kronecker, delta function.
|
||
|
|
||
|
Explanation
|
||
|
===========
|
||
|
|
||
|
A function that takes in two integers $i$ and $j$. It returns $0$ if $i$
|
||
|
and $j$ are not equal, or it returns $1$ if $i$ and $j$ are equal.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
An example with integer indices:
|
||
|
|
||
|
>>> from sympy import KroneckerDelta
|
||
|
>>> KroneckerDelta(1, 2)
|
||
|
0
|
||
|
>>> KroneckerDelta(3, 3)
|
||
|
1
|
||
|
|
||
|
Symbolic indices:
|
||
|
|
||
|
>>> from sympy.abc import i, j, k
|
||
|
>>> KroneckerDelta(i, j)
|
||
|
KroneckerDelta(i, j)
|
||
|
>>> KroneckerDelta(i, i)
|
||
|
1
|
||
|
>>> KroneckerDelta(i, i + 1)
|
||
|
0
|
||
|
>>> KroneckerDelta(i, i + 1 + k)
|
||
|
KroneckerDelta(i, i + k + 1)
|
||
|
|
||
|
Parameters
|
||
|
==========
|
||
|
|
||
|
i : Number, Symbol
|
||
|
The first index of the delta function.
|
||
|
j : Number, Symbol
|
||
|
The second index of the delta function.
|
||
|
|
||
|
See Also
|
||
|
========
|
||
|
|
||
|
eval
|
||
|
DiracDelta
|
||
|
|
||
|
References
|
||
|
==========
|
||
|
|
||
|
.. [1] https://en.wikipedia.org/wiki/Kronecker_delta
|
||
|
|
||
|
"""
|
||
|
|
||
|
is_integer = True
|
||
|
|
||
|
@classmethod
|
||
|
def eval(cls, i, j, delta_range=None):
|
||
|
"""
|
||
|
Evaluates the discrete delta function.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy import KroneckerDelta
|
||
|
>>> from sympy.abc import i, j, k
|
||
|
|
||
|
>>> KroneckerDelta(i, j)
|
||
|
KroneckerDelta(i, j)
|
||
|
>>> KroneckerDelta(i, i)
|
||
|
1
|
||
|
>>> KroneckerDelta(i, i + 1)
|
||
|
0
|
||
|
>>> KroneckerDelta(i, i + 1 + k)
|
||
|
KroneckerDelta(i, i + k + 1)
|
||
|
|
||
|
# indirect doctest
|
||
|
|
||
|
"""
|
||
|
|
||
|
if delta_range is not None:
|
||
|
dinf, dsup = delta_range
|
||
|
if (dinf - i > 0) == True:
|
||
|
return S.Zero
|
||
|
if (dinf - j > 0) == True:
|
||
|
return S.Zero
|
||
|
if (dsup - i < 0) == True:
|
||
|
return S.Zero
|
||
|
if (dsup - j < 0) == True:
|
||
|
return S.Zero
|
||
|
|
||
|
diff = i - j
|
||
|
if diff.is_zero:
|
||
|
return S.One
|
||
|
elif fuzzy_not(diff.is_zero):
|
||
|
return S.Zero
|
||
|
|
||
|
if i.assumptions0.get("below_fermi") and \
|
||
|
j.assumptions0.get("above_fermi"):
|
||
|
return S.Zero
|
||
|
if j.assumptions0.get("below_fermi") and \
|
||
|
i.assumptions0.get("above_fermi"):
|
||
|
return S.Zero
|
||
|
# to make KroneckerDelta canonical
|
||
|
# following lines will check if inputs are in order
|
||
|
# if not, will return KroneckerDelta with correct order
|
||
|
if i != min(i, j, key=default_sort_key):
|
||
|
if delta_range:
|
||
|
return cls(j, i, delta_range)
|
||
|
else:
|
||
|
return cls(j, i)
|
||
|
|
||
|
@property
|
||
|
def delta_range(self):
|
||
|
if len(self.args) > 2:
|
||
|
return self.args[2]
|
||
|
|
||
|
def _eval_power(self, expt):
|
||
|
if expt.is_positive:
|
||
|
return self
|
||
|
if expt.is_negative and expt is not S.NegativeOne:
|
||
|
return 1/self
|
||
|
|
||
|
@property
|
||
|
def is_above_fermi(self):
|
||
|
"""
|
||
|
True if Delta can be non-zero above fermi.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy import KroneckerDelta, Symbol
|
||
|
>>> a = Symbol('a', above_fermi=True)
|
||
|
>>> i = Symbol('i', below_fermi=True)
|
||
|
>>> p = Symbol('p')
|
||
|
>>> q = Symbol('q')
|
||
|
>>> KroneckerDelta(p, a).is_above_fermi
|
||
|
True
|
||
|
>>> KroneckerDelta(p, i).is_above_fermi
|
||
|
False
|
||
|
>>> KroneckerDelta(p, q).is_above_fermi
|
||
|
True
|
||
|
|
||
|
See Also
|
||
|
========
|
||
|
|
||
|
is_below_fermi, is_only_below_fermi, is_only_above_fermi
|
||
|
|
||
|
"""
|
||
|
if self.args[0].assumptions0.get("below_fermi"):
|
||
|
return False
|
||
|
if self.args[1].assumptions0.get("below_fermi"):
|
||
|
return False
|
||
|
return True
|
||
|
|
||
|
@property
|
||
|
def is_below_fermi(self):
|
||
|
"""
|
||
|
True if Delta can be non-zero below fermi.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy import KroneckerDelta, Symbol
|
||
|
>>> a = Symbol('a', above_fermi=True)
|
||
|
>>> i = Symbol('i', below_fermi=True)
|
||
|
>>> p = Symbol('p')
|
||
|
>>> q = Symbol('q')
|
||
|
>>> KroneckerDelta(p, a).is_below_fermi
|
||
|
False
|
||
|
>>> KroneckerDelta(p, i).is_below_fermi
|
||
|
True
|
||
|
>>> KroneckerDelta(p, q).is_below_fermi
|
||
|
True
|
||
|
|
||
|
See Also
|
||
|
========
|
||
|
|
||
|
is_above_fermi, is_only_above_fermi, is_only_below_fermi
|
||
|
|
||
|
"""
|
||
|
if self.args[0].assumptions0.get("above_fermi"):
|
||
|
return False
|
||
|
if self.args[1].assumptions0.get("above_fermi"):
|
||
|
return False
|
||
|
return True
|
||
|
|
||
|
@property
|
||
|
def is_only_above_fermi(self):
|
||
|
"""
|
||
|
True if Delta is restricted to above fermi.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy import KroneckerDelta, Symbol
|
||
|
>>> a = Symbol('a', above_fermi=True)
|
||
|
>>> i = Symbol('i', below_fermi=True)
|
||
|
>>> p = Symbol('p')
|
||
|
>>> q = Symbol('q')
|
||
|
>>> KroneckerDelta(p, a).is_only_above_fermi
|
||
|
True
|
||
|
>>> KroneckerDelta(p, q).is_only_above_fermi
|
||
|
False
|
||
|
>>> KroneckerDelta(p, i).is_only_above_fermi
|
||
|
False
|
||
|
|
||
|
See Also
|
||
|
========
|
||
|
|
||
|
is_above_fermi, is_below_fermi, is_only_below_fermi
|
||
|
|
||
|
"""
|
||
|
return ( self.args[0].assumptions0.get("above_fermi")
|
||
|
or
|
||
|
self.args[1].assumptions0.get("above_fermi")
|
||
|
) or False
|
||
|
|
||
|
@property
|
||
|
def is_only_below_fermi(self):
|
||
|
"""
|
||
|
True if Delta is restricted to below fermi.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy import KroneckerDelta, Symbol
|
||
|
>>> a = Symbol('a', above_fermi=True)
|
||
|
>>> i = Symbol('i', below_fermi=True)
|
||
|
>>> p = Symbol('p')
|
||
|
>>> q = Symbol('q')
|
||
|
>>> KroneckerDelta(p, i).is_only_below_fermi
|
||
|
True
|
||
|
>>> KroneckerDelta(p, q).is_only_below_fermi
|
||
|
False
|
||
|
>>> KroneckerDelta(p, a).is_only_below_fermi
|
||
|
False
|
||
|
|
||
|
See Also
|
||
|
========
|
||
|
|
||
|
is_above_fermi, is_below_fermi, is_only_above_fermi
|
||
|
|
||
|
"""
|
||
|
return ( self.args[0].assumptions0.get("below_fermi")
|
||
|
or
|
||
|
self.args[1].assumptions0.get("below_fermi")
|
||
|
) or False
|
||
|
|
||
|
@property
|
||
|
def indices_contain_equal_information(self):
|
||
|
"""
|
||
|
Returns True if indices are either both above or below fermi.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy import KroneckerDelta, Symbol
|
||
|
>>> a = Symbol('a', above_fermi=True)
|
||
|
>>> i = Symbol('i', below_fermi=True)
|
||
|
>>> p = Symbol('p')
|
||
|
>>> q = Symbol('q')
|
||
|
>>> KroneckerDelta(p, q).indices_contain_equal_information
|
||
|
True
|
||
|
>>> KroneckerDelta(p, q+1).indices_contain_equal_information
|
||
|
True
|
||
|
>>> KroneckerDelta(i, p).indices_contain_equal_information
|
||
|
False
|
||
|
|
||
|
"""
|
||
|
if (self.args[0].assumptions0.get("below_fermi") and
|
||
|
self.args[1].assumptions0.get("below_fermi")):
|
||
|
return True
|
||
|
if (self.args[0].assumptions0.get("above_fermi")
|
||
|
and self.args[1].assumptions0.get("above_fermi")):
|
||
|
return True
|
||
|
|
||
|
# if both indices are general we are True, else false
|
||
|
return self.is_below_fermi and self.is_above_fermi
|
||
|
|
||
|
@property
|
||
|
def preferred_index(self):
|
||
|
"""
|
||
|
Returns the index which is preferred to keep in the final expression.
|
||
|
|
||
|
Explanation
|
||
|
===========
|
||
|
|
||
|
The preferred index is the index with more information regarding fermi
|
||
|
level. If indices contain the same information, 'a' is preferred before
|
||
|
'b'.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy import KroneckerDelta, Symbol
|
||
|
>>> a = Symbol('a', above_fermi=True)
|
||
|
>>> i = Symbol('i', below_fermi=True)
|
||
|
>>> j = Symbol('j', below_fermi=True)
|
||
|
>>> p = Symbol('p')
|
||
|
>>> KroneckerDelta(p, i).preferred_index
|
||
|
i
|
||
|
>>> KroneckerDelta(p, a).preferred_index
|
||
|
a
|
||
|
>>> KroneckerDelta(i, j).preferred_index
|
||
|
i
|
||
|
|
||
|
See Also
|
||
|
========
|
||
|
|
||
|
killable_index
|
||
|
|
||
|
"""
|
||
|
if self._get_preferred_index():
|
||
|
return self.args[1]
|
||
|
else:
|
||
|
return self.args[0]
|
||
|
|
||
|
@property
|
||
|
def killable_index(self):
|
||
|
"""
|
||
|
Returns the index which is preferred to substitute in the final
|
||
|
expression.
|
||
|
|
||
|
Explanation
|
||
|
===========
|
||
|
|
||
|
The index to substitute is the index with less information regarding
|
||
|
fermi level. If indices contain the same information, 'a' is preferred
|
||
|
before 'b'.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy import KroneckerDelta, Symbol
|
||
|
>>> a = Symbol('a', above_fermi=True)
|
||
|
>>> i = Symbol('i', below_fermi=True)
|
||
|
>>> j = Symbol('j', below_fermi=True)
|
||
|
>>> p = Symbol('p')
|
||
|
>>> KroneckerDelta(p, i).killable_index
|
||
|
p
|
||
|
>>> KroneckerDelta(p, a).killable_index
|
||
|
p
|
||
|
>>> KroneckerDelta(i, j).killable_index
|
||
|
j
|
||
|
|
||
|
See Also
|
||
|
========
|
||
|
|
||
|
preferred_index
|
||
|
|
||
|
"""
|
||
|
if self._get_preferred_index():
|
||
|
return self.args[0]
|
||
|
else:
|
||
|
return self.args[1]
|
||
|
|
||
|
def _get_preferred_index(self):
|
||
|
"""
|
||
|
Returns the index which is preferred to keep in the final expression.
|
||
|
|
||
|
The preferred index is the index with more information regarding fermi
|
||
|
level. If indices contain the same information, index 0 is returned.
|
||
|
|
||
|
"""
|
||
|
if not self.is_above_fermi:
|
||
|
if self.args[0].assumptions0.get("below_fermi"):
|
||
|
return 0
|
||
|
else:
|
||
|
return 1
|
||
|
elif not self.is_below_fermi:
|
||
|
if self.args[0].assumptions0.get("above_fermi"):
|
||
|
return 0
|
||
|
else:
|
||
|
return 1
|
||
|
else:
|
||
|
return 0
|
||
|
|
||
|
@property
|
||
|
def indices(self):
|
||
|
return self.args[0:2]
|
||
|
|
||
|
def _eval_rewrite_as_Piecewise(self, *args, **kwargs):
|
||
|
i, j = args
|
||
|
return Piecewise((0, Ne(i, j)), (1, True))
|