| from sympy import permutedims |
| from sympy.core.numbers import Number |
| from sympy.core.singleton import S |
| from sympy.core.symbol import Symbol |
| from sympy.core.sympify import sympify |
| from sympy.tensor.tensor import Tensor, TensExpr, TensAdd, TensMul |
|
|
|
|
| class PartialDerivative(TensExpr): |
| """ |
| Partial derivative for tensor expressions. |
| |
| Examples |
| ======== |
| |
| >>> from sympy.tensor.tensor import TensorIndexType, TensorHead |
| >>> from sympy.tensor.toperators import PartialDerivative |
| >>> from sympy import symbols |
| >>> L = TensorIndexType("L") |
| >>> A = TensorHead("A", [L]) |
| >>> B = TensorHead("B", [L]) |
| >>> i, j, k = symbols("i j k") |
| |
| >>> expr = PartialDerivative(A(i), A(j)) |
| >>> expr |
| PartialDerivative(A(i), A(j)) |
| |
| The ``PartialDerivative`` object behaves like a tensorial expression: |
| |
| >>> expr.get_indices() |
| [i, -j] |
| |
| Notice that the deriving variables have opposite valence than the |
| printed one: ``A(j)`` is printed as covariant, but the index of the |
| derivative is actually contravariant, i.e. ``-j``. |
| |
| Indices can be contracted: |
| |
| >>> expr = PartialDerivative(A(i), A(i)) |
| >>> expr |
| PartialDerivative(A(L_0), A(L_0)) |
| >>> expr.get_indices() |
| [L_0, -L_0] |
| |
| The method ``.get_indices()`` always returns all indices (even the |
| contracted ones). If only uncontracted indices are needed, call |
| ``.get_free_indices()``: |
| |
| >>> expr.get_free_indices() |
| [] |
| |
| Nested partial derivatives are flattened: |
| |
| >>> expr = PartialDerivative(PartialDerivative(A(i), A(j)), A(k)) |
| >>> expr |
| PartialDerivative(A(i), A(j), A(k)) |
| >>> expr.get_indices() |
| [i, -j, -k] |
| |
| Replace a derivative with array values: |
| |
| >>> from sympy.abc import x, y |
| >>> from sympy import sin, log |
| >>> compA = [sin(x), log(x)*y**3] |
| >>> compB = [x, y] |
| >>> expr = PartialDerivative(A(i), B(j)) |
| >>> expr.replace_with_arrays({A(i): compA, B(i): compB}) |
| [[cos(x), 0], [y**3/x, 3*y**2*log(x)]] |
| |
| The returned array is indexed by `(i, -j)`. |
| |
| Be careful that other SymPy modules put the indices of the deriving |
| variables before the indices of the derivand in the derivative result. |
| For example: |
| |
| >>> expr.get_free_indices() |
| [i, -j] |
| |
| >>> from sympy import Matrix, Array |
| >>> Matrix(compA).diff(Matrix(compB)).reshape(2, 2) |
| [[cos(x), y**3/x], [0, 3*y**2*log(x)]] |
| >>> Array(compA).diff(Array(compB)) |
| [[cos(x), y**3/x], [0, 3*y**2*log(x)]] |
| |
| These are the transpose of the result of ``PartialDerivative``, |
| as the matrix and the array modules put the index `-j` before `i` in the |
| derivative result. An array read with index order `(-j, i)` is indeed the |
| transpose of the same array read with index order `(i, -j)`. By specifying |
| the index order to ``.replace_with_arrays`` one can get a compatible |
| expression: |
| |
| >>> expr.replace_with_arrays({A(i): compA, B(i): compB}, [-j, i]) |
| [[cos(x), y**3/x], [0, 3*y**2*log(x)]] |
| """ |
|
|
| def __new__(cls, expr, *variables): |
|
|
| |
| if isinstance(expr, PartialDerivative): |
| variables = expr.variables + variables |
| expr = expr.expr |
|
|
| args, indices, free, dum = cls._contract_indices_for_derivative( |
| S(expr), variables) |
|
|
| obj = TensExpr.__new__(cls, *args) |
|
|
| obj._indices = indices |
| obj._free = free |
| obj._dum = dum |
| return obj |
|
|
| @property |
| def coeff(self): |
| return S.One |
|
|
| @property |
| def nocoeff(self): |
| return self |
|
|
| @classmethod |
| def _contract_indices_for_derivative(cls, expr, variables): |
| variables_opposite_valence = [] |
|
|
| for i in variables: |
| if isinstance(i, Tensor): |
| i_free_indices = i.get_free_indices() |
| variables_opposite_valence.append( |
| i.xreplace({k: -k for k in i_free_indices})) |
| elif isinstance(i, Symbol): |
| variables_opposite_valence.append(i) |
|
|
| args, indices, free, dum = TensMul._tensMul_contract_indices( |
| [expr] + variables_opposite_valence, replace_indices=True) |
|
|
| for i in range(1, len(args)): |
| args_i = args[i] |
| if isinstance(args_i, Tensor): |
| i_indices = args[i].get_free_indices() |
| args[i] = args[i].xreplace({k: -k for k in i_indices}) |
|
|
| return args, indices, free, dum |
|
|
| def doit(self, **hints): |
| args, indices, free, dum = self._contract_indices_for_derivative(self.expr, self.variables) |
|
|
| obj = self.func(*args) |
| obj._indices = indices |
| obj._free = free |
| obj._dum = dum |
|
|
| return obj |
|
|
| def _expand_partial_derivative(self): |
| args, indices, free, dum = self._contract_indices_for_derivative(self.expr, self.variables) |
|
|
| obj = self.func(*args) |
| obj._indices = indices |
| obj._free = free |
| obj._dum = dum |
|
|
| result = obj |
|
|
| if not args[0].free_symbols: |
| return S.Zero |
| elif isinstance(obj.expr, TensAdd): |
| |
| result = obj.expr.func(*[ |
| self.func(a, *obj.variables)._expand_partial_derivative() |
| for a in result.expr.args]) |
| elif isinstance(obj.expr, TensMul): |
| |
| if len(obj.variables) == 1: |
| |
| terms = [] |
| mulargs = list(obj.expr.args) |
| for ind in range(len(mulargs)): |
| if not isinstance(sympify(mulargs[ind]), Number): |
| |
| |
| d = self.func(mulargs[ind], *obj.variables)._expand_partial_derivative() |
| terms.append(TensMul(*(mulargs[:ind] |
| + [d] |
| + mulargs[(ind + 1):]))) |
| result = TensAdd.fromiter(terms) |
| else: |
| |
| |
| |
| |
| result = obj.expr |
| for v in obj.variables: |
| result = self.func(result, v)._expand_partial_derivative() |
| |
|
|
| return result |
|
|
| def _perform_derivative(self): |
| result = self.expr |
| for v in self.variables: |
| if isinstance(result, TensExpr): |
| result = result._eval_partial_derivative(v) |
| else: |
| if v._diff_wrt: |
| result = result._eval_derivative(v) |
| else: |
| result = S.Zero |
| return result |
|
|
| def get_indices(self): |
| return self._indices |
|
|
| def get_free_indices(self): |
| free = sorted(self._free, key=lambda x: x[1]) |
| return [i[0] for i in free] |
|
|
| def _replace_indices(self, repl): |
| expr = self.expr.xreplace(repl) |
| mirrored = {-k: -v for k, v in repl.items()} |
| variables = [i.xreplace(mirrored) for i in self.variables] |
| return self.func(expr, *variables) |
|
|
| @property |
| def expr(self): |
| return self.args[0] |
|
|
| @property |
| def variables(self): |
| return self.args[1:] |
|
|
| def _extract_data(self, replacement_dict): |
| from .array import derive_by_array, tensorcontraction |
| indices, array = self.expr._extract_data(replacement_dict) |
| for variable in self.variables: |
| var_indices, var_array = variable._extract_data(replacement_dict) |
| var_indices = [-i for i in var_indices] |
| coeff_array, var_array = zip(*[i.as_coeff_Mul() for i in var_array]) |
| dim_before = len(array.shape) |
| array = derive_by_array(array, var_array) |
| dim_after = len(array.shape) |
| dim_increase = dim_after - dim_before |
| array = permutedims(array, [i + dim_increase for i in range(dim_before)] + list(range(dim_increase))) |
| array = array.as_mutable() |
| varindex = var_indices[0] |
| |
| coeff_index = [0] + [slice(None) for i in range(len(indices))] |
| for i, coeff in enumerate(coeff_array): |
| coeff_index[0] = i |
| array[tuple(coeff_index)] /= coeff |
| if -varindex in indices: |
| pos = indices.index(-varindex) |
| array = tensorcontraction(array, (0, pos+1)) |
| indices.pop(pos) |
| else: |
| indices.append(varindex) |
| return indices, array |
|
|