Spaces:
Sleeping
Sleeping
| """ | |
| This module defines tensors with abstract index notation. | |
| The abstract index notation has been first formalized by Penrose. | |
| Tensor indices are formal objects, with a tensor type; there is no | |
| notion of index range, it is only possible to assign the dimension, | |
| used to trace the Kronecker delta; the dimension can be a Symbol. | |
| The Einstein summation convention is used. | |
| The covariant indices are indicated with a minus sign in front of the index. | |
| For instance the tensor ``t = p(a)*A(b,c)*q(-c)`` has the index ``c`` | |
| contracted. | |
| A tensor expression ``t`` can be called; called with its | |
| indices in sorted order it is equal to itself: | |
| in the above example ``t(a, b) == t``; | |
| one can call ``t`` with different indices; ``t(c, d) == p(c)*A(d,a)*q(-a)``. | |
| The contracted indices are dummy indices, internally they have no name, | |
| the indices being represented by a graph-like structure. | |
| Tensors are put in canonical form using ``canon_bp``, which uses | |
| the Butler-Portugal algorithm for canonicalization using the monoterm | |
| symmetries of the tensors. | |
| If there is a (anti)symmetric metric, the indices can be raised and | |
| lowered when the tensor is put in canonical form. | |
| """ | |
| from __future__ import annotations | |
| from typing import Any | |
| from functools import reduce | |
| from math import prod | |
| from abc import abstractmethod, ABC | |
| from collections import defaultdict | |
| import operator | |
| import itertools | |
| from sympy.core.numbers import (Integer, Rational) | |
| from sympy.combinatorics import Permutation | |
| from sympy.combinatorics.tensor_can import get_symmetric_group_sgs, \ | |
| bsgs_direct_product, canonicalize, riemann_bsgs | |
| from sympy.core import Basic, Expr, sympify, Add, Mul, S | |
| from sympy.core.cache import clear_cache | |
| from sympy.core.containers import Tuple, Dict | |
| from sympy.core.sorting import default_sort_key | |
| from sympy.core.symbol import Symbol, symbols | |
| from sympy.core.sympify import CantSympify, _sympify | |
| from sympy.core.operations import AssocOp | |
| from sympy.external.gmpy import SYMPY_INTS | |
| from sympy.matrices import eye | |
| from sympy.utilities.exceptions import (sympy_deprecation_warning, | |
| SymPyDeprecationWarning, | |
| ignore_warnings) | |
| from sympy.utilities.decorator import memoize_property, deprecated | |
| from sympy.utilities.iterables import sift | |
| def deprecate_data(): | |
| sympy_deprecation_warning( | |
| """ | |
| The data attribute of TensorIndexType is deprecated. Use The | |
| replace_with_arrays() method instead. | |
| """, | |
| deprecated_since_version="1.4", | |
| active_deprecations_target="deprecated-tensorindextype-attrs", | |
| stacklevel=4, | |
| ) | |
| def deprecate_fun_eval(): | |
| sympy_deprecation_warning( | |
| """ | |
| The Tensor.fun_eval() method is deprecated. Use | |
| Tensor.substitute_indices() instead. | |
| """, | |
| deprecated_since_version="1.5", | |
| active_deprecations_target="deprecated-tensor-fun-eval", | |
| stacklevel=4, | |
| ) | |
| def deprecate_call(): | |
| sympy_deprecation_warning( | |
| """ | |
| Calling a tensor like Tensor(*indices) is deprecated. Use | |
| Tensor.substitute_indices() instead. | |
| """, | |
| deprecated_since_version="1.5", | |
| active_deprecations_target="deprecated-tensor-fun-eval", | |
| stacklevel=4, | |
| ) | |
| class _IndexStructure(CantSympify): | |
| """ | |
| This class handles the indices (free and dummy ones). It contains the | |
| algorithms to manage the dummy indices replacements and contractions of | |
| free indices under multiplications of tensor expressions, as well as stuff | |
| related to canonicalization sorting, getting the permutation of the | |
| expression and so on. It also includes tools to get the ``TensorIndex`` | |
| objects corresponding to the given index structure. | |
| """ | |
| def __init__(self, free, dum, index_types, indices, canon_bp=False): | |
| self.free = free | |
| self.dum = dum | |
| self.index_types = index_types | |
| self.indices = indices | |
| self._ext_rank = len(self.free) + 2*len(self.dum) | |
| self.dum.sort(key=lambda x: x[0]) | |
| def from_indices(*indices): | |
| """ | |
| Create a new ``_IndexStructure`` object from a list of ``indices``. | |
| Explanation | |
| =========== | |
| ``indices`` ``TensorIndex`` objects, the indices. Contractions are | |
| detected upon construction. | |
| Examples | |
| ======== | |
| >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, _IndexStructure | |
| >>> Lorentz = TensorIndexType('Lorentz', dummy_name='L') | |
| >>> m0, m1, m2, m3 = tensor_indices('m0,m1,m2,m3', Lorentz) | |
| >>> _IndexStructure.from_indices(m0, m1, -m1, m3) | |
| _IndexStructure([(m0, 0), (m3, 3)], [(1, 2)], [Lorentz, Lorentz, Lorentz, Lorentz]) | |
| """ | |
| free, dum = _IndexStructure._free_dum_from_indices(*indices) | |
| index_types = [i.tensor_index_type for i in indices] | |
| indices = _IndexStructure._replace_dummy_names(indices, free, dum) | |
| return _IndexStructure(free, dum, index_types, indices) | |
| def from_components_free_dum(components, free, dum): | |
| index_types = [] | |
| for component in components: | |
| index_types.extend(component.index_types) | |
| indices = _IndexStructure.generate_indices_from_free_dum_index_types(free, dum, index_types) | |
| return _IndexStructure(free, dum, index_types, indices) | |
| def _free_dum_from_indices(*indices): | |
| """ | |
| Convert ``indices`` into ``free``, ``dum`` for single component tensor. | |
| Explanation | |
| =========== | |
| ``free`` list of tuples ``(index, pos, 0)``, | |
| where ``pos`` is the position of index in | |
| the list of indices formed by the component tensors | |
| ``dum`` list of tuples ``(pos_contr, pos_cov, 0, 0)`` | |
| Examples | |
| ======== | |
| >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, \ | |
| _IndexStructure | |
| >>> Lorentz = TensorIndexType('Lorentz', dummy_name='L') | |
| >>> m0, m1, m2, m3 = tensor_indices('m0,m1,m2,m3', Lorentz) | |
| >>> _IndexStructure._free_dum_from_indices(m0, m1, -m1, m3) | |
| ([(m0, 0), (m3, 3)], [(1, 2)]) | |
| """ | |
| n = len(indices) | |
| if n == 1: | |
| return [(indices[0], 0)], [] | |
| # find the positions of the free indices and of the dummy indices | |
| free = [True]*len(indices) | |
| index_dict = {} | |
| dum = [] | |
| for i, index in enumerate(indices): | |
| name = index.name | |
| typ = index.tensor_index_type | |
| contr = index.is_up | |
| if (name, typ) in index_dict: | |
| # found a pair of dummy indices | |
| is_contr, pos = index_dict[(name, typ)] | |
| # check consistency and update free | |
| if is_contr: | |
| if contr: | |
| raise ValueError('two equal contravariant indices in slots %d and %d' %(pos, i)) | |
| else: | |
| free[pos] = False | |
| free[i] = False | |
| else: | |
| if contr: | |
| free[pos] = False | |
| free[i] = False | |
| else: | |
| raise ValueError('two equal covariant indices in slots %d and %d' %(pos, i)) | |
| if contr: | |
| dum.append((i, pos)) | |
| else: | |
| dum.append((pos, i)) | |
| else: | |
| index_dict[(name, typ)] = index.is_up, i | |
| free = [(index, i) for i, index in enumerate(indices) if free[i]] | |
| free.sort() | |
| return free, dum | |
| def get_indices(self): | |
| """ | |
| Get a list of indices, creating new tensor indices to complete dummy indices. | |
| """ | |
| return self.indices[:] | |
| def generate_indices_from_free_dum_index_types(free, dum, index_types): | |
| indices = [None]*(len(free)+2*len(dum)) | |
| for idx, pos in free: | |
| indices[pos] = idx | |
| generate_dummy_name = _IndexStructure._get_generator_for_dummy_indices(free) | |
| for pos1, pos2 in dum: | |
| typ1 = index_types[pos1] | |
| indname = generate_dummy_name(typ1) | |
| indices[pos1] = TensorIndex(indname, typ1, True) | |
| indices[pos2] = TensorIndex(indname, typ1, False) | |
| return _IndexStructure._replace_dummy_names(indices, free, dum) | |
| def _get_generator_for_dummy_indices(free): | |
| cdt = defaultdict(int) | |
| # if the free indices have names with dummy_name, start with an | |
| # index higher than those for the dummy indices | |
| # to avoid name collisions | |
| for indx, ipos in free: | |
| if indx.name.split('_')[0] == indx.tensor_index_type.dummy_name: | |
| cdt[indx.tensor_index_type] = max(cdt[indx.tensor_index_type], int(indx.name.split('_')[1]) + 1) | |
| def dummy_name_gen(tensor_index_type): | |
| nd = str(cdt[tensor_index_type]) | |
| cdt[tensor_index_type] += 1 | |
| return tensor_index_type.dummy_name + '_' + nd | |
| return dummy_name_gen | |
| def _replace_dummy_names(indices, free, dum): | |
| dum.sort(key=lambda x: x[0]) | |
| new_indices = list(indices) | |
| assert len(indices) == len(free) + 2*len(dum) | |
| generate_dummy_name = _IndexStructure._get_generator_for_dummy_indices(free) | |
| for ipos1, ipos2 in dum: | |
| typ1 = new_indices[ipos1].tensor_index_type | |
| indname = generate_dummy_name(typ1) | |
| new_indices[ipos1] = TensorIndex(indname, typ1, True) | |
| new_indices[ipos2] = TensorIndex(indname, typ1, False) | |
| return new_indices | |
| def get_free_indices(self) -> list[TensorIndex]: | |
| """ | |
| Get a list of free indices. | |
| """ | |
| # get sorted indices according to their position: | |
| free = sorted(self.free, key=lambda x: x[1]) | |
| return [i[0] for i in free] | |
| def __str__(self): | |
| return "_IndexStructure({}, {}, {})".format(self.free, self.dum, self.index_types) | |
| def __repr__(self): | |
| return self.__str__() | |
| def _get_sorted_free_indices_for_canon(self): | |
| sorted_free = self.free[:] | |
| sorted_free.sort(key=lambda x: x[0]) | |
| return sorted_free | |
| def _get_sorted_dum_indices_for_canon(self): | |
| return sorted(self.dum, key=lambda x: x[0]) | |
| def _get_lexicographically_sorted_index_types(self): | |
| permutation = self.indices_canon_args()[0] | |
| index_types = [None]*self._ext_rank | |
| for i, it in enumerate(self.index_types): | |
| index_types[permutation(i)] = it | |
| return index_types | |
| def _get_lexicographically_sorted_indices(self): | |
| permutation = self.indices_canon_args()[0] | |
| indices = [None]*self._ext_rank | |
| for i, it in enumerate(self.indices): | |
| indices[permutation(i)] = it | |
| return indices | |
| def perm2tensor(self, g, is_canon_bp=False): | |
| """ | |
| Returns a ``_IndexStructure`` instance corresponding to the permutation ``g``. | |
| Explanation | |
| =========== | |
| ``g`` permutation corresponding to the tensor in the representation | |
| used in canonicalization | |
| ``is_canon_bp`` if True, then ``g`` is the permutation | |
| corresponding to the canonical form of the tensor | |
| """ | |
| sorted_free = [i[0] for i in self._get_sorted_free_indices_for_canon()] | |
| lex_index_types = self._get_lexicographically_sorted_index_types() | |
| lex_indices = self._get_lexicographically_sorted_indices() | |
| nfree = len(sorted_free) | |
| rank = self._ext_rank | |
| dum = [[None]*2 for i in range((rank - nfree)//2)] | |
| free = [] | |
| index_types = [None]*rank | |
| indices = [None]*rank | |
| for i in range(rank): | |
| gi = g[i] | |
| index_types[i] = lex_index_types[gi] | |
| indices[i] = lex_indices[gi] | |
| if gi < nfree: | |
| ind = sorted_free[gi] | |
| assert index_types[i] == sorted_free[gi].tensor_index_type | |
| free.append((ind, i)) | |
| else: | |
| j = gi - nfree | |
| idum, cov = divmod(j, 2) | |
| if cov: | |
| dum[idum][1] = i | |
| else: | |
| dum[idum][0] = i | |
| dum = [tuple(x) for x in dum] | |
| return _IndexStructure(free, dum, index_types, indices) | |
| def indices_canon_args(self): | |
| """ | |
| Returns ``(g, dummies, msym, v)``, the entries of ``canonicalize`` | |
| See ``canonicalize`` in ``tensor_can.py`` in combinatorics module. | |
| """ | |
| # to be called after sorted_components | |
| from sympy.combinatorics.permutations import _af_new | |
| n = self._ext_rank | |
| g = [None]*n + [n, n+1] | |
| # Converts the symmetry of the metric into msym from .canonicalize() | |
| # method in the combinatorics module | |
| def metric_symmetry_to_msym(metric): | |
| if metric is None: | |
| return None | |
| sym = metric.symmetry | |
| if sym == TensorSymmetry.fully_symmetric(2): | |
| return 0 | |
| if sym == TensorSymmetry.fully_symmetric(-2): | |
| return 1 | |
| return None | |
| # ordered indices: first the free indices, ordered by types | |
| # then the dummy indices, ordered by types and contravariant before | |
| # covariant | |
| # g[position in tensor] = position in ordered indices | |
| for i, (indx, ipos) in enumerate(self._get_sorted_free_indices_for_canon()): | |
| g[ipos] = i | |
| pos = len(self.free) | |
| j = len(self.free) | |
| dummies = [] | |
| prev = None | |
| a = [] | |
| msym = [] | |
| for ipos1, ipos2 in self._get_sorted_dum_indices_for_canon(): | |
| g[ipos1] = j | |
| g[ipos2] = j + 1 | |
| j += 2 | |
| typ = self.index_types[ipos1] | |
| if typ != prev: | |
| if a: | |
| dummies.append(a) | |
| a = [pos, pos + 1] | |
| prev = typ | |
| msym.append(metric_symmetry_to_msym(typ.metric)) | |
| else: | |
| a.extend([pos, pos + 1]) | |
| pos += 2 | |
| if a: | |
| dummies.append(a) | |
| return _af_new(g), dummies, msym | |
| def components_canon_args(components): | |
| numtyp = [] | |
| prev = None | |
| for t in components: | |
| if t == prev: | |
| numtyp[-1][1] += 1 | |
| else: | |
| prev = t | |
| numtyp.append([prev, 1]) | |
| v = [] | |
| for h, n in numtyp: | |
| if h.comm in (0, 1): | |
| comm = h.comm | |
| else: | |
| comm = TensorManager.get_comm(h.comm, h.comm) | |
| v.append((h.symmetry.base, h.symmetry.generators, n, comm)) | |
| return v | |
| class _TensorDataLazyEvaluator(CantSympify): | |
| """ | |
| EXPERIMENTAL: do not rely on this class, it may change without deprecation | |
| warnings in future versions of SymPy. | |
| Explanation | |
| =========== | |
| This object contains the logic to associate components data to a tensor | |
| expression. Components data are set via the ``.data`` property of tensor | |
| expressions, is stored inside this class as a mapping between the tensor | |
| expression and the ``ndarray``. | |
| Computations are executed lazily: whereas the tensor expressions can have | |
| contractions, tensor products, and additions, components data are not | |
| computed until they are accessed by reading the ``.data`` property | |
| associated to the tensor expression. | |
| """ | |
| _substitutions_dict: dict[Any, Any] = {} | |
| _substitutions_dict_tensmul: dict[Any, Any] = {} | |
| def __getitem__(self, key): | |
| dat = self._get(key) | |
| if dat is None: | |
| return None | |
| from .array import NDimArray | |
| if not isinstance(dat, NDimArray): | |
| return dat | |
| if dat.rank() == 0: | |
| return dat[()] | |
| elif dat.rank() == 1 and len(dat) == 1: | |
| return dat[0] | |
| return dat | |
| def _get(self, key): | |
| """ | |
| Retrieve ``data`` associated with ``key``. | |
| Explanation | |
| =========== | |
| This algorithm looks into ``self._substitutions_dict`` for all | |
| ``TensorHead`` in the ``TensExpr`` (or just ``TensorHead`` if key is a | |
| TensorHead instance). It reconstructs the components data that the | |
| tensor expression should have by performing on components data the | |
| operations that correspond to the abstract tensor operations applied. | |
| Metric tensor is handled in a different manner: it is pre-computed in | |
| ``self._substitutions_dict_tensmul``. | |
| """ | |
| if key in self._substitutions_dict: | |
| return self._substitutions_dict[key] | |
| if isinstance(key, TensorHead): | |
| return None | |
| if isinstance(key, Tensor): | |
| # special case to handle metrics. Metric tensors cannot be | |
| # constructed through contraction by the metric, their | |
| # components show if they are a matrix or its inverse. | |
| signature = tuple([i.is_up for i in key.get_indices()]) | |
| srch = (key.component,) + signature | |
| if srch in self._substitutions_dict_tensmul: | |
| return self._substitutions_dict_tensmul[srch] | |
| array_list = [self.data_from_tensor(key)] | |
| return self.data_contract_dum(array_list, key.dum, key.ext_rank) | |
| if isinstance(key, TensMul): | |
| tensmul_args = key.args | |
| if len(tensmul_args) == 1 and len(tensmul_args[0].components) == 1: | |
| # special case to handle metrics. Metric tensors cannot be | |
| # constructed through contraction by the metric, their | |
| # components show if they are a matrix or its inverse. | |
| signature = tuple([i.is_up for i in tensmul_args[0].get_indices()]) | |
| srch = (tensmul_args[0].components[0],) + signature | |
| if srch in self._substitutions_dict_tensmul: | |
| return self._substitutions_dict_tensmul[srch] | |
| #data_list = [self.data_from_tensor(i) for i in tensmul_args if isinstance(i, TensExpr)] | |
| data_list = [self.data_from_tensor(i) if isinstance(i, Tensor) else i.data for i in tensmul_args if isinstance(i, TensExpr)] | |
| coeff = prod([i for i in tensmul_args if not isinstance(i, TensExpr)]) | |
| if all(i is None for i in data_list): | |
| return None | |
| if any(i is None for i in data_list): | |
| raise ValueError("Mixing tensors with associated components "\ | |
| "data with tensors without components data") | |
| data_result = self.data_contract_dum(data_list, key.dum, key.ext_rank) | |
| return coeff*data_result | |
| if isinstance(key, TensAdd): | |
| data_list = [] | |
| free_args_list = [] | |
| for arg in key.args: | |
| if isinstance(arg, TensExpr): | |
| data_list.append(arg.data) | |
| free_args_list.append([x[0] for x in arg.free]) | |
| else: | |
| data_list.append(arg) | |
| free_args_list.append([]) | |
| if all(i is None for i in data_list): | |
| return None | |
| if any(i is None for i in data_list): | |
| raise ValueError("Mixing tensors with associated components "\ | |
| "data with tensors without components data") | |
| sum_list = [] | |
| from .array import permutedims | |
| for data, free_args in zip(data_list, free_args_list): | |
| if len(free_args) < 2: | |
| sum_list.append(data) | |
| else: | |
| free_args_pos = {y: x for x, y in enumerate(free_args)} | |
| axes = [free_args_pos[arg] for arg in key.free_args] | |
| sum_list.append(permutedims(data, axes)) | |
| return reduce(lambda x, y: x+y, sum_list) | |
| return None | |
| def data_contract_dum(ndarray_list, dum, ext_rank): | |
| from .array import tensorproduct, tensorcontraction, MutableDenseNDimArray | |
| arrays = list(map(MutableDenseNDimArray, ndarray_list)) | |
| prodarr = tensorproduct(*arrays) | |
| return tensorcontraction(prodarr, *dum) | |
| def data_tensorhead_from_tensmul(self, data, tensmul, tensorhead): | |
| """ | |
| This method is used when assigning components data to a ``TensMul`` | |
| object, it converts components data to a fully contravariant ndarray, | |
| which is then stored according to the ``TensorHead`` key. | |
| """ | |
| if data is None: | |
| return None | |
| return self._correct_signature_from_indices( | |
| data, | |
| tensmul.get_indices(), | |
| tensmul.free, | |
| tensmul.dum, | |
| True) | |
| def data_from_tensor(self, tensor): | |
| """ | |
| This method corrects the components data to the right signature | |
| (covariant/contravariant) using the metric associated with each | |
| ``TensorIndexType``. | |
| """ | |
| tensorhead = tensor.component | |
| if tensorhead.data is None: | |
| return None | |
| return self._correct_signature_from_indices( | |
| tensorhead.data, | |
| tensor.get_indices(), | |
| tensor.free, | |
| tensor.dum) | |
| def _assign_data_to_tensor_expr(self, key, data): | |
| if isinstance(key, TensAdd): | |
| raise ValueError('cannot assign data to TensAdd') | |
| # here it is assumed that `key` is a `TensMul` instance. | |
| if len(key.components) != 1: | |
| raise ValueError('cannot assign data to TensMul with multiple components') | |
| tensorhead = key.components[0] | |
| newdata = self.data_tensorhead_from_tensmul(data, key, tensorhead) | |
| return tensorhead, newdata | |
| def _check_permutations_on_data(self, tens, data): | |
| from .array import permutedims | |
| from .array.arrayop import Flatten | |
| if isinstance(tens, TensorHead): | |
| rank = tens.rank | |
| generators = tens.symmetry.generators | |
| elif isinstance(tens, Tensor): | |
| rank = tens.rank | |
| generators = tens.components[0].symmetry.generators | |
| elif isinstance(tens, TensorIndexType): | |
| rank = tens.metric.rank | |
| generators = tens.metric.symmetry.generators | |
| # Every generator is a permutation, check that by permuting the array | |
| # by that permutation, the array will be the same, except for a | |
| # possible sign change if the permutation admits it. | |
| for gener in generators: | |
| sign_change = +1 if (gener(rank) == rank) else -1 | |
| data_swapped = data | |
| last_data = data | |
| permute_axes = list(map(gener, range(rank))) | |
| # the order of a permutation is the number of times to get the | |
| # identity by applying that permutation. | |
| for i in range(gener.order()-1): | |
| data_swapped = permutedims(data_swapped, permute_axes) | |
| # if any value in the difference array is non-zero, raise an error: | |
| if any(Flatten(last_data - sign_change*data_swapped)): | |
| raise ValueError("Component data symmetry structure error") | |
| last_data = data_swapped | |
| def __setitem__(self, key, value): | |
| """ | |
| Set the components data of a tensor object/expression. | |
| Explanation | |
| =========== | |
| Components data are transformed to the all-contravariant form and stored | |
| with the corresponding ``TensorHead`` object. If a ``TensorHead`` object | |
| cannot be uniquely identified, it will raise an error. | |
| """ | |
| data = _TensorDataLazyEvaluator.parse_data(value) | |
| self._check_permutations_on_data(key, data) | |
| # TensorHead and TensorIndexType can be assigned data directly, while | |
| # TensMul must first convert data to a fully contravariant form, and | |
| # assign it to its corresponding TensorHead single component. | |
| if not isinstance(key, (TensorHead, TensorIndexType)): | |
| key, data = self._assign_data_to_tensor_expr(key, data) | |
| if isinstance(key, TensorHead): | |
| for dim, indextype in zip(data.shape, key.index_types): | |
| if indextype.data is None: | |
| raise ValueError("index type {} has no components data"\ | |
| " associated (needed to raise/lower index)".format(indextype)) | |
| if not indextype.dim.is_number: | |
| continue | |
| if dim != indextype.dim: | |
| raise ValueError("wrong dimension of ndarray") | |
| self._substitutions_dict[key] = data | |
| def __delitem__(self, key): | |
| del self._substitutions_dict[key] | |
| def __contains__(self, key): | |
| return key in self._substitutions_dict | |
| def add_metric_data(self, metric, data): | |
| """ | |
| Assign data to the ``metric`` tensor. The metric tensor behaves in an | |
| anomalous way when raising and lowering indices. | |
| Explanation | |
| =========== | |
| A fully covariant metric is the inverse transpose of the fully | |
| contravariant metric (it is meant matrix inverse). If the metric is | |
| symmetric, the transpose is not necessary and mixed | |
| covariant/contravariant metrics are Kronecker deltas. | |
| """ | |
| # hard assignment, data should not be added to `TensorHead` for metric: | |
| # the problem with `TensorHead` is that the metric is anomalous, i.e. | |
| # raising and lowering the index means considering the metric or its | |
| # inverse, this is not the case for other tensors. | |
| self._substitutions_dict_tensmul[metric, True, True] = data | |
| inverse_transpose = self.inverse_transpose_matrix(data) | |
| # in symmetric spaces, the transpose is the same as the original matrix, | |
| # the full covariant metric tensor is the inverse transpose, so this | |
| # code will be able to handle non-symmetric metrics. | |
| self._substitutions_dict_tensmul[metric, False, False] = inverse_transpose | |
| # now mixed cases, these are identical to the unit matrix if the metric | |
| # is symmetric. | |
| m = data.tomatrix() | |
| invt = inverse_transpose.tomatrix() | |
| self._substitutions_dict_tensmul[metric, True, False] = m * invt | |
| self._substitutions_dict_tensmul[metric, False, True] = invt * m | |
| def _flip_index_by_metric(data, metric, pos): | |
| from .array import tensorproduct, tensorcontraction | |
| mdim = metric.rank() | |
| ddim = data.rank() | |
| if pos == 0: | |
| data = tensorcontraction( | |
| tensorproduct( | |
| metric, | |
| data | |
| ), | |
| (1, mdim+pos) | |
| ) | |
| else: | |
| data = tensorcontraction( | |
| tensorproduct( | |
| data, | |
| metric | |
| ), | |
| (pos, ddim) | |
| ) | |
| return data | |
| def inverse_matrix(ndarray): | |
| m = ndarray.tomatrix().inv() | |
| return _TensorDataLazyEvaluator.parse_data(m) | |
| def inverse_transpose_matrix(ndarray): | |
| m = ndarray.tomatrix().inv().T | |
| return _TensorDataLazyEvaluator.parse_data(m) | |
| def _correct_signature_from_indices(data, indices, free, dum, inverse=False): | |
| """ | |
| Utility function to correct the values inside the components data | |
| ndarray according to whether indices are covariant or contravariant. | |
| It uses the metric matrix to lower values of covariant indices. | |
| """ | |
| # change the ndarray values according covariantness/contravariantness of the indices | |
| # use the metric | |
| for i, indx in enumerate(indices): | |
| if not indx.is_up and not inverse: | |
| data = _TensorDataLazyEvaluator._flip_index_by_metric(data, indx.tensor_index_type.data, i) | |
| elif not indx.is_up and inverse: | |
| data = _TensorDataLazyEvaluator._flip_index_by_metric( | |
| data, | |
| _TensorDataLazyEvaluator.inverse_matrix(indx.tensor_index_type.data), | |
| i | |
| ) | |
| return data | |
| def _sort_data_axes(old, new): | |
| from .array import permutedims | |
| new_data = old.data.copy() | |
| old_free = [i[0] for i in old.free] | |
| new_free = [i[0] for i in new.free] | |
| for i in range(len(new_free)): | |
| for j in range(i, len(old_free)): | |
| if old_free[j] == new_free[i]: | |
| old_free[i], old_free[j] = old_free[j], old_free[i] | |
| new_data = permutedims(new_data, (i, j)) | |
| break | |
| return new_data | |
| def add_rearrange_tensmul_parts(new_tensmul, old_tensmul): | |
| def sorted_compo(): | |
| return _TensorDataLazyEvaluator._sort_data_axes(old_tensmul, new_tensmul) | |
| _TensorDataLazyEvaluator._substitutions_dict[new_tensmul] = sorted_compo() | |
| def parse_data(data): | |
| """ | |
| Transform ``data`` to array. The parameter ``data`` may | |
| contain data in various formats, e.g. nested lists, SymPy ``Matrix``, | |
| and so on. | |
| Examples | |
| ======== | |
| >>> from sympy.tensor.tensor import _TensorDataLazyEvaluator | |
| >>> _TensorDataLazyEvaluator.parse_data([1, 3, -6, 12]) | |
| [1, 3, -6, 12] | |
| >>> _TensorDataLazyEvaluator.parse_data([[1, 2], [4, 7]]) | |
| [[1, 2], [4, 7]] | |
| """ | |
| from .array import MutableDenseNDimArray | |
| if not isinstance(data, MutableDenseNDimArray): | |
| if len(data) == 2 and hasattr(data[0], '__call__'): | |
| data = MutableDenseNDimArray(data[0], data[1]) | |
| else: | |
| data = MutableDenseNDimArray(data) | |
| return data | |
| _tensor_data_substitution_dict = _TensorDataLazyEvaluator() | |
| class _TensorManager: | |
| """ | |
| Class to manage tensor properties. | |
| Notes | |
| ===== | |
| Tensors belong to tensor commutation groups; each group has a label | |
| ``comm``; there are predefined labels: | |
| ``0`` tensors commuting with any other tensor | |
| ``1`` tensors anticommuting among themselves | |
| ``2`` tensors not commuting, apart with those with ``comm=0`` | |
| Other groups can be defined using ``set_comm``; tensors in those | |
| groups commute with those with ``comm=0``; by default they | |
| do not commute with any other group. | |
| """ | |
| def __init__(self): | |
| self._comm_init() | |
| def _comm_init(self): | |
| self._comm = [{} for i in range(3)] | |
| for i in range(3): | |
| self._comm[0][i] = 0 | |
| self._comm[i][0] = 0 | |
| self._comm[1][1] = 1 | |
| self._comm[2][1] = None | |
| self._comm[1][2] = None | |
| self._comm_symbols2i = {0:0, 1:1, 2:2} | |
| self._comm_i2symbol = {0:0, 1:1, 2:2} | |
| def comm(self): | |
| return self._comm | |
| def comm_symbols2i(self, i): | |
| """ | |
| Get the commutation group number corresponding to ``i``. | |
| ``i`` can be a symbol or a number or a string. | |
| If ``i`` is not already defined its commutation group number | |
| is set. | |
| """ | |
| if i not in self._comm_symbols2i: | |
| n = len(self._comm) | |
| self._comm.append({}) | |
| self._comm[n][0] = 0 | |
| self._comm[0][n] = 0 | |
| self._comm_symbols2i[i] = n | |
| self._comm_i2symbol[n] = i | |
| return n | |
| return self._comm_symbols2i[i] | |
| def comm_i2symbol(self, i): | |
| """ | |
| Returns the symbol corresponding to the commutation group number. | |
| """ | |
| return self._comm_i2symbol[i] | |
| def set_comm(self, i, j, c): | |
| """ | |
| Set the commutation parameter ``c`` for commutation groups ``i, j``. | |
| Parameters | |
| ========== | |
| i, j : symbols representing commutation groups | |
| c : group commutation number | |
| Notes | |
| ===== | |
| ``i, j`` can be symbols, strings or numbers, | |
| apart from ``0, 1`` and ``2`` which are reserved respectively | |
| for commuting, anticommuting tensors and tensors not commuting | |
| with any other group apart with the commuting tensors. | |
| For the remaining cases, use this method to set the commutation rules; | |
| by default ``c=None``. | |
| The group commutation number ``c`` is assigned in correspondence | |
| to the group commutation symbols; it can be | |
| 0 commuting | |
| 1 anticommuting | |
| None no commutation property | |
| Examples | |
| ======== | |
| ``G`` and ``GH`` do not commute with themselves and commute with | |
| each other; A is commuting. | |
| >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, TensorHead, TensorManager, TensorSymmetry | |
| >>> Lorentz = TensorIndexType('Lorentz') | |
| >>> i0,i1,i2,i3,i4 = tensor_indices('i0:5', Lorentz) | |
| >>> A = TensorHead('A', [Lorentz]) | |
| >>> G = TensorHead('G', [Lorentz], TensorSymmetry.no_symmetry(1), 'Gcomm') | |
| >>> GH = TensorHead('GH', [Lorentz], TensorSymmetry.no_symmetry(1), 'GHcomm') | |
| >>> TensorManager.set_comm('Gcomm', 'GHcomm', 0) | |
| >>> (GH(i1)*G(i0)).canon_bp() | |
| G(i0)*GH(i1) | |
| >>> (G(i1)*G(i0)).canon_bp() | |
| G(i1)*G(i0) | |
| >>> (G(i1)*A(i0)).canon_bp() | |
| A(i0)*G(i1) | |
| """ | |
| if c not in (0, 1, None): | |
| raise ValueError('`c` can assume only the values 0, 1 or None') | |
| i = sympify(i) | |
| j = sympify(j) | |
| if i not in self._comm_symbols2i: | |
| n = len(self._comm) | |
| self._comm.append({}) | |
| self._comm[n][0] = 0 | |
| self._comm[0][n] = 0 | |
| self._comm_symbols2i[i] = n | |
| self._comm_i2symbol[n] = i | |
| if j not in self._comm_symbols2i: | |
| n = len(self._comm) | |
| self._comm.append({}) | |
| self._comm[0][n] = 0 | |
| self._comm[n][0] = 0 | |
| self._comm_symbols2i[j] = n | |
| self._comm_i2symbol[n] = j | |
| ni = self._comm_symbols2i[i] | |
| nj = self._comm_symbols2i[j] | |
| self._comm[ni][nj] = c | |
| self._comm[nj][ni] = c | |
| """ | |
| Cached sympy functions (e.g. expand) may have cached the results of | |
| expressions involving tensors, but those results may not be valid after | |
| changing the commutation properties. To stay on the safe side, we clear | |
| the cache of all functions. | |
| """ | |
| clear_cache() | |
| def set_comms(self, *args): | |
| """ | |
| Set the commutation group numbers ``c`` for symbols ``i, j``. | |
| Parameters | |
| ========== | |
| args : sequence of ``(i, j, c)`` | |
| """ | |
| for i, j, c in args: | |
| self.set_comm(i, j, c) | |
| def get_comm(self, i, j): | |
| """ | |
| Return the commutation parameter for commutation group numbers ``i, j`` | |
| see ``_TensorManager.set_comm`` | |
| """ | |
| return self._comm[i].get(j, 0 if i == 0 or j == 0 else None) | |
| def clear(self): | |
| """ | |
| Clear the TensorManager. | |
| """ | |
| self._comm_init() | |
| TensorManager = _TensorManager() | |
| class TensorIndexType(Basic): | |
| """ | |
| A TensorIndexType is characterized by its name and its metric. | |
| Parameters | |
| ========== | |
| name : name of the tensor type | |
| dummy_name : name of the head of dummy indices | |
| dim : dimension, it can be a symbol or an integer or ``None`` | |
| eps_dim : dimension of the epsilon tensor | |
| metric_symmetry : integer that denotes metric symmetry or ``None`` for no metric | |
| metric_name : string with the name of the metric tensor | |
| Attributes | |
| ========== | |
| ``metric`` : the metric tensor | |
| ``delta`` : ``Kronecker delta`` | |
| ``epsilon`` : the ``Levi-Civita epsilon`` tensor | |
| ``data`` : (deprecated) a property to add ``ndarray`` values, to work in a specified basis. | |
| Notes | |
| ===== | |
| The possible values of the ``metric_symmetry`` parameter are: | |
| ``1`` : metric tensor is fully symmetric | |
| ``0`` : metric tensor possesses no index symmetry | |
| ``-1`` : metric tensor is fully antisymmetric | |
| ``None``: there is no metric tensor (metric equals to ``None``) | |
| The metric is assumed to be symmetric by default. It can also be set | |
| to a custom tensor by the ``.set_metric()`` method. | |
| If there is a metric the metric is used to raise and lower indices. | |
| In the case of non-symmetric metric, the following raising and | |
| lowering conventions will be adopted: | |
| ``psi(a) = g(a, b)*psi(-b); chi(-a) = chi(b)*g(-b, -a)`` | |
| From these it is easy to find: | |
| ``g(-a, b) = delta(-a, b)`` | |
| where ``delta(-a, b) = delta(b, -a)`` is the ``Kronecker delta`` | |
| (see ``TensorIndex`` for the conventions on indices). | |
| For antisymmetric metrics there is also the following equality: | |
| ``g(a, -b) = -delta(a, -b)`` | |
| If there is no metric it is not possible to raise or lower indices; | |
| e.g. the index of the defining representation of ``SU(N)`` | |
| is 'covariant' and the conjugate representation is | |
| 'contravariant'; for ``N > 2`` they are linearly independent. | |
| ``eps_dim`` is by default equal to ``dim``, if the latter is an integer; | |
| else it can be assigned (for use in naive dimensional regularization); | |
| if ``eps_dim`` is not an integer ``epsilon`` is ``None``. | |
| Examples | |
| ======== | |
| >>> from sympy.tensor.tensor import TensorIndexType | |
| >>> Lorentz = TensorIndexType('Lorentz', dummy_name='L') | |
| >>> Lorentz.metric | |
| metric(Lorentz,Lorentz) | |
| """ | |
| def __new__(cls, name, dummy_name=None, dim=None, eps_dim=None, | |
| metric_symmetry=1, metric_name='metric', **kwargs): | |
| if 'dummy_fmt' in kwargs: | |
| dummy_fmt = kwargs['dummy_fmt'] | |
| sympy_deprecation_warning( | |
| f""" | |
| The dummy_fmt keyword to TensorIndexType is deprecated. Use | |
| dummy_name={dummy_fmt} instead. | |
| """, | |
| deprecated_since_version="1.5", | |
| active_deprecations_target="deprecated-tensorindextype-dummy-fmt", | |
| ) | |
| dummy_name = dummy_fmt | |
| if isinstance(name, str): | |
| name = Symbol(name) | |
| if dummy_name is None: | |
| dummy_name = str(name)[0] | |
| if isinstance(dummy_name, str): | |
| dummy_name = Symbol(dummy_name) | |
| if dim is None: | |
| dim = Symbol("dim_" + dummy_name.name) | |
| else: | |
| dim = sympify(dim) | |
| if eps_dim is None: | |
| eps_dim = dim | |
| else: | |
| eps_dim = sympify(eps_dim) | |
| metric_symmetry = sympify(metric_symmetry) | |
| if isinstance(metric_name, str): | |
| metric_name = Symbol(metric_name) | |
| if 'metric' in kwargs: | |
| SymPyDeprecationWarning( | |
| """ | |
| The 'metric' keyword argument to TensorIndexType is | |
| deprecated. Use the 'metric_symmetry' keyword argument or the | |
| TensorIndexType.set_metric() method instead. | |
| """, | |
| deprecated_since_version="1.5", | |
| active_deprecations_target="deprecated-tensorindextype-metric", | |
| ) | |
| metric = kwargs.get('metric') | |
| if metric is not None: | |
| if metric in (True, False, 0, 1): | |
| metric_name = 'metric' | |
| #metric_antisym = metric | |
| else: | |
| metric_name = metric.name | |
| #metric_antisym = metric.antisym | |
| if metric: | |
| metric_symmetry = -1 | |
| else: | |
| metric_symmetry = 1 | |
| obj = Basic.__new__(cls, name, dummy_name, dim, eps_dim, | |
| metric_symmetry, metric_name) | |
| obj._autogenerated = [] | |
| return obj | |
| def name(self): | |
| return self.args[0].name | |
| def dummy_name(self): | |
| return self.args[1].name | |
| def dim(self): | |
| return self.args[2] | |
| def eps_dim(self): | |
| return self.args[3] | |
| def metric(self): | |
| metric_symmetry = self.args[4] | |
| metric_name = self.args[5] | |
| if metric_symmetry is None: | |
| return None | |
| if metric_symmetry == 0: | |
| symmetry = TensorSymmetry.no_symmetry(2) | |
| elif metric_symmetry == 1: | |
| symmetry = TensorSymmetry.fully_symmetric(2) | |
| elif metric_symmetry == -1: | |
| symmetry = TensorSymmetry.fully_symmetric(-2) | |
| return TensorHead(metric_name, [self]*2, symmetry) | |
| def delta(self): | |
| return TensorHead('KD', [self]*2, TensorSymmetry.fully_symmetric(2)) | |
| def epsilon(self): | |
| if not isinstance(self.eps_dim, (SYMPY_INTS, Integer)): | |
| return None | |
| symmetry = TensorSymmetry.fully_symmetric(-self.eps_dim) | |
| return TensorHead('Eps', [self]*self.eps_dim, symmetry) | |
| def set_metric(self, tensor): | |
| self._metric = tensor | |
| def __lt__(self, other): | |
| return self.name < other.name | |
| def __str__(self): | |
| return self.name | |
| __repr__ = __str__ | |
| # Everything below this line is deprecated | |
| def data(self): | |
| deprecate_data() | |
| with ignore_warnings(SymPyDeprecationWarning): | |
| return _tensor_data_substitution_dict[self] | |
| def data(self, data): | |
| deprecate_data() | |
| # This assignment is a bit controversial, should metric components be assigned | |
| # to the metric only or also to the TensorIndexType object? The advantage here | |
| # is the ability to assign a 1D array and transform it to a 2D diagonal array. | |
| from .array import MutableDenseNDimArray | |
| data = _TensorDataLazyEvaluator.parse_data(data) | |
| if data.rank() > 2: | |
| raise ValueError("data have to be of rank 1 (diagonal metric) or 2.") | |
| if data.rank() == 1: | |
| if self.dim.is_number: | |
| nda_dim = data.shape[0] | |
| if nda_dim != self.dim: | |
| raise ValueError("Dimension mismatch") | |
| dim = data.shape[0] | |
| newndarray = MutableDenseNDimArray.zeros(dim, dim) | |
| for i, val in enumerate(data): | |
| newndarray[i, i] = val | |
| data = newndarray | |
| dim1, dim2 = data.shape | |
| if dim1 != dim2: | |
| raise ValueError("Non-square matrix tensor.") | |
| if self.dim.is_number: | |
| if self.dim != dim1: | |
| raise ValueError("Dimension mismatch") | |
| _tensor_data_substitution_dict[self] = data | |
| _tensor_data_substitution_dict.add_metric_data(self.metric, data) | |
| with ignore_warnings(SymPyDeprecationWarning): | |
| delta = self.get_kronecker_delta() | |
| i1 = TensorIndex('i1', self) | |
| i2 = TensorIndex('i2', self) | |
| with ignore_warnings(SymPyDeprecationWarning): | |
| delta(i1, -i2).data = _TensorDataLazyEvaluator.parse_data(eye(dim1)) | |
| def data(self): | |
| deprecate_data() | |
| with ignore_warnings(SymPyDeprecationWarning): | |
| if self in _tensor_data_substitution_dict: | |
| del _tensor_data_substitution_dict[self] | |
| if self.metric in _tensor_data_substitution_dict: | |
| del _tensor_data_substitution_dict[self.metric] | |
| def get_kronecker_delta(self): | |
| sym2 = TensorSymmetry(get_symmetric_group_sgs(2)) | |
| delta = TensorHead('KD', [self]*2, sym2) | |
| return delta | |
| def get_epsilon(self): | |
| if not isinstance(self._eps_dim, (SYMPY_INTS, Integer)): | |
| return None | |
| sym = TensorSymmetry(get_symmetric_group_sgs(self._eps_dim, 1)) | |
| epsilon = TensorHead('Eps', [self]*self._eps_dim, sym) | |
| return epsilon | |
| def _components_data_full_destroy(self): | |
| """ | |
| EXPERIMENTAL: do not rely on this API method. | |
| This destroys components data associated to the ``TensorIndexType``, if | |
| any, specifically: | |
| * metric tensor data | |
| * Kronecker tensor data | |
| """ | |
| if self in _tensor_data_substitution_dict: | |
| del _tensor_data_substitution_dict[self] | |
| def delete_tensmul_data(key): | |
| if key in _tensor_data_substitution_dict._substitutions_dict_tensmul: | |
| del _tensor_data_substitution_dict._substitutions_dict_tensmul[key] | |
| # delete metric data: | |
| delete_tensmul_data((self.metric, True, True)) | |
| delete_tensmul_data((self.metric, True, False)) | |
| delete_tensmul_data((self.metric, False, True)) | |
| delete_tensmul_data((self.metric, False, False)) | |
| # delete delta tensor data: | |
| delta = self.get_kronecker_delta() | |
| if delta in _tensor_data_substitution_dict: | |
| del _tensor_data_substitution_dict[delta] | |
| class TensorIndex(Basic): | |
| """ | |
| Represents a tensor index | |
| Parameters | |
| ========== | |
| name : name of the index, or ``True`` if you want it to be automatically assigned | |
| tensor_index_type : ``TensorIndexType`` of the index | |
| is_up : flag for contravariant index (is_up=True by default) | |
| Attributes | |
| ========== | |
| ``name`` | |
| ``tensor_index_type`` | |
| ``is_up`` | |
| Notes | |
| ===== | |
| Tensor indices are contracted with the Einstein summation convention. | |
| An index can be in contravariant or in covariant form; in the latter | |
| case it is represented prepending a ``-`` to the index name. Adding | |
| ``-`` to a covariant (is_up=False) index makes it contravariant. | |
| Dummy indices have a name with head given by | |
| ``tensor_inde_type.dummy_name`` with underscore and a number. | |
| Similar to ``symbols`` multiple contravariant indices can be created | |
| at once using ``tensor_indices(s, typ)``, where ``s`` is a string | |
| of names. | |
| Examples | |
| ======== | |
| >>> from sympy.tensor.tensor import TensorIndexType, TensorIndex, TensorHead, tensor_indices | |
| >>> Lorentz = TensorIndexType('Lorentz', dummy_name='L') | |
| >>> mu = TensorIndex('mu', Lorentz, is_up=False) | |
| >>> nu, rho = tensor_indices('nu, rho', Lorentz) | |
| >>> A = TensorHead('A', [Lorentz, Lorentz]) | |
| >>> A(mu, nu) | |
| A(-mu, nu) | |
| >>> A(-mu, -rho) | |
| A(mu, -rho) | |
| >>> A(mu, -mu) | |
| A(-L_0, L_0) | |
| """ | |
| def __new__(cls, name, tensor_index_type, is_up=True): | |
| if isinstance(name, str): | |
| name_symbol = Symbol(name) | |
| elif isinstance(name, Symbol): | |
| name_symbol = name | |
| elif name is True: | |
| name = "_i{}".format(len(tensor_index_type._autogenerated)) | |
| name_symbol = Symbol(name) | |
| tensor_index_type._autogenerated.append(name_symbol) | |
| else: | |
| raise ValueError("invalid name") | |
| is_up = sympify(is_up) | |
| return Basic.__new__(cls, name_symbol, tensor_index_type, is_up) | |
| def name(self): | |
| return self.args[0].name | |
| def tensor_index_type(self): | |
| return self.args[1] | |
| def is_up(self): | |
| return self.args[2] | |
| def _print(self): | |
| s = self.name | |
| if not self.is_up: | |
| s = '-%s' % s | |
| return s | |
| def __lt__(self, other): | |
| return ((self.tensor_index_type, self.name) < | |
| (other.tensor_index_type, other.name)) | |
| def __neg__(self): | |
| t1 = TensorIndex(self.name, self.tensor_index_type, | |
| (not self.is_up)) | |
| return t1 | |
| def tensor_indices(s, typ): | |
| """ | |
| Returns list of tensor indices given their names and their types. | |
| Parameters | |
| ========== | |
| s : string of comma separated names of indices | |
| typ : ``TensorIndexType`` of the indices | |
| Examples | |
| ======== | |
| >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices | |
| >>> Lorentz = TensorIndexType('Lorentz', dummy_name='L') | |
| >>> a, b, c, d = tensor_indices('a,b,c,d', Lorentz) | |
| """ | |
| if isinstance(s, str): | |
| a = [x.name for x in symbols(s, seq=True)] | |
| else: | |
| raise ValueError('expecting a string') | |
| tilist = [TensorIndex(i, typ) for i in a] | |
| if len(tilist) == 1: | |
| return tilist[0] | |
| return tilist | |
| class TensorSymmetry(Basic): | |
| """ | |
| Monoterm symmetry of a tensor (i.e. any symmetric or anti-symmetric | |
| index permutation). For the relevant terminology see ``tensor_can.py`` | |
| section of the combinatorics module. | |
| Parameters | |
| ========== | |
| bsgs : tuple ``(base, sgs)`` BSGS of the symmetry of the tensor | |
| Attributes | |
| ========== | |
| ``base`` : base of the BSGS | |
| ``generators`` : generators of the BSGS | |
| ``rank`` : rank of the tensor | |
| Notes | |
| ===== | |
| A tensor can have an arbitrary monoterm symmetry provided by its BSGS. | |
| Multiterm symmetries, like the cyclic symmetry of the Riemann tensor | |
| (i.e., Bianchi identity), are not covered. See combinatorics module for | |
| information on how to generate BSGS for a general index permutation group. | |
| Simple symmetries can be generated using built-in methods. | |
| See Also | |
| ======== | |
| sympy.combinatorics.tensor_can.get_symmetric_group_sgs | |
| Examples | |
| ======== | |
| Define a symmetric tensor of rank 2 | |
| >>> from sympy.tensor.tensor import TensorIndexType, TensorSymmetry, get_symmetric_group_sgs, TensorHead | |
| >>> Lorentz = TensorIndexType('Lorentz', dummy_name='L') | |
| >>> sym = TensorSymmetry(get_symmetric_group_sgs(2)) | |
| >>> T = TensorHead('T', [Lorentz]*2, sym) | |
| Note, that the same can also be done using built-in TensorSymmetry methods | |
| >>> sym2 = TensorSymmetry.fully_symmetric(2) | |
| >>> sym == sym2 | |
| True | |
| """ | |
| def __new__(cls, *args, **kw_args): | |
| if len(args) == 1: | |
| base, generators = args[0] | |
| elif len(args) == 2: | |
| base, generators = args | |
| else: | |
| raise TypeError("bsgs required, either two separate parameters or one tuple") | |
| if not isinstance(base, Tuple): | |
| base = Tuple(*base) | |
| if not isinstance(generators, Tuple): | |
| generators = Tuple(*generators) | |
| return Basic.__new__(cls, base, generators, **kw_args) | |
| def base(self): | |
| return self.args[0] | |
| def generators(self): | |
| return self.args[1] | |
| def rank(self): | |
| return self.generators[0].size - 2 | |
| def fully_symmetric(cls, rank): | |
| """ | |
| Returns a fully symmetric (antisymmetric if ``rank``<0) | |
| TensorSymmetry object for ``abs(rank)`` indices. | |
| """ | |
| if rank > 0: | |
| bsgs = get_symmetric_group_sgs(rank, False) | |
| elif rank < 0: | |
| bsgs = get_symmetric_group_sgs(-rank, True) | |
| elif rank == 0: | |
| bsgs = ([], [Permutation(1)]) | |
| return TensorSymmetry(bsgs) | |
| def direct_product(cls, *args): | |
| """ | |
| Returns a TensorSymmetry object that is being a direct product of | |
| fully (anti-)symmetric index permutation groups. | |
| Notes | |
| ===== | |
| Some examples for different values of ``(*args)``: | |
| ``(1)`` vector, equivalent to ``TensorSymmetry.fully_symmetric(1)`` | |
| ``(2)`` tensor with 2 symmetric indices, equivalent to ``.fully_symmetric(2)`` | |
| ``(-2)`` tensor with 2 antisymmetric indices, equivalent to ``.fully_symmetric(-2)`` | |
| ``(2, -2)`` tensor with the first 2 indices commuting and the last 2 anticommuting | |
| ``(1, 1, 1)`` tensor with 3 indices without any symmetry | |
| """ | |
| base, sgs = [], [Permutation(1)] | |
| for arg in args: | |
| if arg > 0: | |
| bsgs2 = get_symmetric_group_sgs(arg, False) | |
| elif arg < 0: | |
| bsgs2 = get_symmetric_group_sgs(-arg, True) | |
| else: | |
| continue | |
| base, sgs = bsgs_direct_product(base, sgs, *bsgs2) | |
| return TensorSymmetry(base, sgs) | |
| def riemann(cls): | |
| """ | |
| Returns a monotorem symmetry of the Riemann tensor | |
| """ | |
| return TensorSymmetry(riemann_bsgs) | |
| def no_symmetry(cls, rank): | |
| """ | |
| TensorSymmetry object for ``rank`` indices with no symmetry | |
| """ | |
| return TensorSymmetry([], [Permutation(rank+1)]) | |
| def tensorsymmetry(*args): | |
| """ | |
| Returns a ``TensorSymmetry`` object. This method is deprecated, use | |
| ``TensorSymmetry.direct_product()`` or ``.riemann()`` instead. | |
| Explanation | |
| =========== | |
| One can represent a tensor with any monoterm slot symmetry group | |
| using a BSGS. | |
| ``args`` can be a BSGS | |
| ``args[0]`` base | |
| ``args[1]`` sgs | |
| Usually tensors are in (direct products of) representations | |
| of the symmetric group; | |
| ``args`` can be a list of lists representing the shapes of Young tableaux | |
| Notes | |
| ===== | |
| For instance: | |
| ``[[1]]`` vector | |
| ``[[1]*n]`` symmetric tensor of rank ``n`` | |
| ``[[n]]`` antisymmetric tensor of rank ``n`` | |
| ``[[2, 2]]`` monoterm slot symmetry of the Riemann tensor | |
| ``[[1],[1]]`` vector*vector | |
| ``[[2],[1],[1]`` (antisymmetric tensor)*vector*vector | |
| Notice that with the shape ``[2, 2]`` we associate only the monoterm | |
| symmetries of the Riemann tensor; this is an abuse of notation, | |
| since the shape ``[2, 2]`` corresponds usually to the irreducible | |
| representation characterized by the monoterm symmetries and by the | |
| cyclic symmetry. | |
| """ | |
| from sympy.combinatorics import Permutation | |
| def tableau2bsgs(a): | |
| if len(a) == 1: | |
| # antisymmetric vector | |
| n = a[0] | |
| bsgs = get_symmetric_group_sgs(n, 1) | |
| else: | |
| if all(x == 1 for x in a): | |
| # symmetric vector | |
| n = len(a) | |
| bsgs = get_symmetric_group_sgs(n) | |
| elif a == [2, 2]: | |
| bsgs = riemann_bsgs | |
| else: | |
| raise NotImplementedError | |
| return bsgs | |
| if not args: | |
| return TensorSymmetry(Tuple(), Tuple(Permutation(1))) | |
| if len(args) == 2 and isinstance(args[1][0], Permutation): | |
| return TensorSymmetry(args) | |
| base, sgs = tableau2bsgs(args[0]) | |
| for a in args[1:]: | |
| basex, sgsx = tableau2bsgs(a) | |
| base, sgs = bsgs_direct_product(base, sgs, basex, sgsx) | |
| return TensorSymmetry(Tuple(base, sgs)) | |
| class TensorType(Basic): | |
| """ | |
| Class of tensor types. Deprecated, use tensor_heads() instead. | |
| Parameters | |
| ========== | |
| index_types : list of ``TensorIndexType`` of the tensor indices | |
| symmetry : ``TensorSymmetry`` of the tensor | |
| Attributes | |
| ========== | |
| ``index_types`` | |
| ``symmetry`` | |
| ``types`` : list of ``TensorIndexType`` without repetitions | |
| """ | |
| is_commutative = False | |
| def __new__(cls, index_types, symmetry, **kw_args): | |
| assert symmetry.rank == len(index_types) | |
| obj = Basic.__new__(cls, Tuple(*index_types), symmetry, **kw_args) | |
| return obj | |
| def index_types(self): | |
| return self.args[0] | |
| def symmetry(self): | |
| return self.args[1] | |
| def types(self): | |
| return sorted(set(self.index_types), key=lambda x: x.name) | |
| def __str__(self): | |
| return 'TensorType(%s)' % ([str(x) for x in self.index_types]) | |
| def __call__(self, s, comm=0): | |
| """ | |
| Return a TensorHead object or a list of TensorHead objects. | |
| Parameters | |
| ========== | |
| s : name or string of names. | |
| comm : Commutation group. | |
| see ``_TensorManager.set_comm`` | |
| """ | |
| if isinstance(s, str): | |
| names = [x.name for x in symbols(s, seq=True)] | |
| else: | |
| raise ValueError('expecting a string') | |
| if len(names) == 1: | |
| return TensorHead(names[0], self.index_types, self.symmetry, comm) | |
| else: | |
| return [TensorHead(name, self.index_types, self.symmetry, comm) for name in names] | |
| def tensorhead(name, typ, sym=None, comm=0): | |
| """ | |
| Function generating tensorhead(s). This method is deprecated, | |
| use TensorHead constructor or tensor_heads() instead. | |
| Parameters | |
| ========== | |
| name : name or sequence of names (as in ``symbols``) | |
| typ : index types | |
| sym : same as ``*args`` in ``tensorsymmetry`` | |
| comm : commutation group number | |
| see ``_TensorManager.set_comm`` | |
| """ | |
| if sym is None: | |
| sym = [[1] for i in range(len(typ))] | |
| with ignore_warnings(SymPyDeprecationWarning): | |
| sym = tensorsymmetry(*sym) | |
| return TensorHead(name, typ, sym, comm) | |
| class TensorHead(Basic): | |
| """ | |
| Tensor head of the tensor. | |
| Parameters | |
| ========== | |
| name : name of the tensor | |
| index_types : list of TensorIndexType | |
| symmetry : TensorSymmetry of the tensor | |
| comm : commutation group number | |
| Attributes | |
| ========== | |
| ``name`` | |
| ``index_types`` | |
| ``rank`` : total number of indices | |
| ``symmetry`` | |
| ``comm`` : commutation group | |
| Notes | |
| ===== | |
| Similar to ``symbols`` multiple TensorHeads can be created using | |
| ``tensorhead(s, typ, sym=None, comm=0)`` function, where ``s`` | |
| is the string of names and ``sym`` is the monoterm tensor symmetry | |
| (see ``tensorsymmetry``). | |
| A ``TensorHead`` belongs to a commutation group, defined by a | |
| symbol on number ``comm`` (see ``_TensorManager.set_comm``); | |
| tensors in a commutation group have the same commutation properties; | |
| by default ``comm`` is ``0``, the group of the commuting tensors. | |
| Examples | |
| ======== | |
| Define a fully antisymmetric tensor of rank 2: | |
| >>> from sympy.tensor.tensor import TensorIndexType, TensorHead, TensorSymmetry | |
| >>> Lorentz = TensorIndexType('Lorentz', dummy_name='L') | |
| >>> asym2 = TensorSymmetry.fully_symmetric(-2) | |
| >>> A = TensorHead('A', [Lorentz, Lorentz], asym2) | |
| Examples with ndarray values, the components data assigned to the | |
| ``TensorHead`` object are assumed to be in a fully-contravariant | |
| representation. In case it is necessary to assign components data which | |
| represents the values of a non-fully covariant tensor, see the other | |
| examples. | |
| >>> from sympy.tensor.tensor import tensor_indices | |
| >>> from sympy import diag | |
| >>> Lorentz = TensorIndexType('Lorentz', dummy_name='L') | |
| >>> i0, i1 = tensor_indices('i0:2', Lorentz) | |
| Specify a replacement dictionary to keep track of the arrays to use for | |
| replacements in the tensorial expression. The ``TensorIndexType`` is | |
| associated to the metric used for contractions (in fully covariant form): | |
| >>> repl = {Lorentz: diag(1, -1, -1, -1)} | |
| Let's see some examples of working with components with the electromagnetic | |
| tensor: | |
| >>> from sympy import symbols | |
| >>> Ex, Ey, Ez, Bx, By, Bz = symbols('E_x E_y E_z B_x B_y B_z') | |
| >>> c = symbols('c', positive=True) | |
| Let's define `F`, an antisymmetric tensor: | |
| >>> F = TensorHead('F', [Lorentz, Lorentz], asym2) | |
| Let's update the dictionary to contain the matrix to use in the | |
| replacements: | |
| >>> repl.update({F(-i0, -i1): [ | |
| ... [0, Ex/c, Ey/c, Ez/c], | |
| ... [-Ex/c, 0, -Bz, By], | |
| ... [-Ey/c, Bz, 0, -Bx], | |
| ... [-Ez/c, -By, Bx, 0]]}) | |
| Now it is possible to retrieve the contravariant form of the Electromagnetic | |
| tensor: | |
| >>> F(i0, i1).replace_with_arrays(repl, [i0, i1]) | |
| [[0, -E_x/c, -E_y/c, -E_z/c], [E_x/c, 0, -B_z, B_y], [E_y/c, B_z, 0, -B_x], [E_z/c, -B_y, B_x, 0]] | |
| and the mixed contravariant-covariant form: | |
| >>> F(i0, -i1).replace_with_arrays(repl, [i0, -i1]) | |
| [[0, E_x/c, E_y/c, E_z/c], [E_x/c, 0, B_z, -B_y], [E_y/c, -B_z, 0, B_x], [E_z/c, B_y, -B_x, 0]] | |
| Energy-momentum of a particle may be represented as: | |
| >>> from sympy import symbols | |
| >>> P = TensorHead('P', [Lorentz], TensorSymmetry.no_symmetry(1)) | |
| >>> E, px, py, pz = symbols('E p_x p_y p_z', positive=True) | |
| >>> repl.update({P(i0): [E, px, py, pz]}) | |
| The contravariant and covariant components are, respectively: | |
| >>> P(i0).replace_with_arrays(repl, [i0]) | |
| [E, p_x, p_y, p_z] | |
| >>> P(-i0).replace_with_arrays(repl, [-i0]) | |
| [E, -p_x, -p_y, -p_z] | |
| The contraction of a 1-index tensor by itself: | |
| >>> expr = P(i0)*P(-i0) | |
| >>> expr.replace_with_arrays(repl, []) | |
| E**2 - p_x**2 - p_y**2 - p_z**2 | |
| """ | |
| is_commutative = False | |
| def __new__(cls, name, index_types, symmetry=None, comm=0): | |
| if isinstance(name, str): | |
| name_symbol = Symbol(name) | |
| elif isinstance(name, Symbol): | |
| name_symbol = name | |
| else: | |
| raise ValueError("invalid name") | |
| if symmetry is None: | |
| symmetry = TensorSymmetry.no_symmetry(len(index_types)) | |
| else: | |
| assert symmetry.rank == len(index_types) | |
| obj = Basic.__new__(cls, name_symbol, Tuple(*index_types), symmetry, sympify(comm)) | |
| return obj | |
| def name(self): | |
| return self.args[0].name | |
| def index_types(self): | |
| return list(self.args[1]) | |
| def symmetry(self): | |
| return self.args[2] | |
| def comm(self): | |
| return TensorManager.comm_symbols2i(self.args[3]) | |
| def rank(self): | |
| return len(self.index_types) | |
| def __lt__(self, other): | |
| return (self.name, self.index_types) < (other.name, other.index_types) | |
| def commutes_with(self, other): | |
| """ | |
| Returns ``0`` if ``self`` and ``other`` commute, ``1`` if they anticommute. | |
| Returns ``None`` if ``self`` and ``other`` neither commute nor anticommute. | |
| """ | |
| r = TensorManager.get_comm(self.comm, other.comm) | |
| return r | |
| def _print(self): | |
| return '%s(%s)' %(self.name, ','.join([str(x) for x in self.index_types])) | |
| def __call__(self, *indices, **kw_args): | |
| """ | |
| Returns a tensor with indices. | |
| Explanation | |
| =========== | |
| There is a special behavior in case of indices denoted by ``True``, | |
| they are considered auto-matrix indices, their slots are automatically | |
| filled, and confer to the tensor the behavior of a matrix or vector | |
| upon multiplication with another tensor containing auto-matrix indices | |
| of the same ``TensorIndexType``. This means indices get summed over the | |
| same way as in matrix multiplication. For matrix behavior, define two | |
| auto-matrix indices, for vector behavior define just one. | |
| Indices can also be strings, in which case the attribute | |
| ``index_types`` is used to convert them to proper ``TensorIndex``. | |
| Examples | |
| ======== | |
| >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, TensorSymmetry, TensorHead | |
| >>> Lorentz = TensorIndexType('Lorentz', dummy_name='L') | |
| >>> a, b = tensor_indices('a,b', Lorentz) | |
| >>> A = TensorHead('A', [Lorentz]*2, TensorSymmetry.no_symmetry(2)) | |
| >>> t = A(a, -b) | |
| >>> t | |
| A(a, -b) | |
| """ | |
| updated_indices = [] | |
| for idx, typ in zip(indices, self.index_types): | |
| if isinstance(idx, str): | |
| idx = idx.strip().replace(" ", "") | |
| if idx.startswith('-'): | |
| updated_indices.append(TensorIndex(idx[1:], typ, | |
| is_up=False)) | |
| else: | |
| updated_indices.append(TensorIndex(idx, typ)) | |
| else: | |
| updated_indices.append(idx) | |
| updated_indices += indices[len(updated_indices):] | |
| tensor = Tensor(self, updated_indices, **kw_args) | |
| return tensor.doit() | |
| # Everything below this line is deprecated | |
| def __pow__(self, other): | |
| deprecate_data() | |
| with ignore_warnings(SymPyDeprecationWarning): | |
| if self.data is None: | |
| raise ValueError("No power on abstract tensors.") | |
| from .array import tensorproduct, tensorcontraction | |
| metrics = [_.data for _ in self.index_types] | |
| marray = self.data | |
| marraydim = marray.rank() | |
| for metric in metrics: | |
| marray = tensorproduct(marray, metric, marray) | |
| marray = tensorcontraction(marray, (0, marraydim), (marraydim+1, marraydim+2)) | |
| return marray ** (other * S.Half) | |
| def data(self): | |
| deprecate_data() | |
| with ignore_warnings(SymPyDeprecationWarning): | |
| return _tensor_data_substitution_dict[self] | |
| def data(self, data): | |
| deprecate_data() | |
| with ignore_warnings(SymPyDeprecationWarning): | |
| _tensor_data_substitution_dict[self] = data | |
| def data(self): | |
| deprecate_data() | |
| if self in _tensor_data_substitution_dict: | |
| del _tensor_data_substitution_dict[self] | |
| def __iter__(self): | |
| deprecate_data() | |
| with ignore_warnings(SymPyDeprecationWarning): | |
| return self.data.__iter__() | |
| def _components_data_full_destroy(self): | |
| """ | |
| EXPERIMENTAL: do not rely on this API method. | |
| Destroy components data associated to the ``TensorHead`` object, this | |
| checks for attached components data, and destroys components data too. | |
| """ | |
| # do not garbage collect Kronecker tensor (it should be done by | |
| # ``TensorIndexType`` garbage collection) | |
| deprecate_data() | |
| if self.name == "KD": | |
| return | |
| # the data attached to a tensor must be deleted only by the TensorHead | |
| # destructor. If the TensorHead is deleted, it means that there are no | |
| # more instances of that tensor anywhere. | |
| if self in _tensor_data_substitution_dict: | |
| del _tensor_data_substitution_dict[self] | |
| def tensor_heads(s, index_types, symmetry=None, comm=0): | |
| """ | |
| Returns a sequence of TensorHeads from a string `s` | |
| """ | |
| if isinstance(s, str): | |
| names = [x.name for x in symbols(s, seq=True)] | |
| else: | |
| raise ValueError('expecting a string') | |
| thlist = [TensorHead(name, index_types, symmetry, comm) for name in names] | |
| if len(thlist) == 1: | |
| return thlist[0] | |
| return thlist | |
| class TensExpr(Expr, ABC): | |
| """ | |
| Abstract base class for tensor expressions | |
| Notes | |
| ===== | |
| A tensor expression is an expression formed by tensors; | |
| currently the sums of tensors are distributed. | |
| A ``TensExpr`` can be a ``TensAdd`` or a ``TensMul``. | |
| ``TensMul`` objects are formed by products of component tensors, | |
| and include a coefficient, which is a SymPy expression. | |
| In the internal representation contracted indices are represented | |
| by ``(ipos1, ipos2, icomp1, icomp2)``, where ``icomp1`` is the position | |
| of the component tensor with contravariant index, ``ipos1`` is the | |
| slot which the index occupies in that component tensor. | |
| Contracted indices are therefore nameless in the internal representation. | |
| """ | |
| _op_priority = 12.0 | |
| is_commutative = False | |
| def __neg__(self): | |
| return self*S.NegativeOne | |
| def __abs__(self): | |
| raise NotImplementedError | |
| def __add__(self, other): | |
| return TensAdd(self, other).doit() | |
| def __radd__(self, other): | |
| return TensAdd(other, self).doit() | |
| def __sub__(self, other): | |
| return TensAdd(self, -other).doit() | |
| def __rsub__(self, other): | |
| return TensAdd(other, -self).doit() | |
| def __mul__(self, other): | |
| """ | |
| Multiply two tensors using Einstein summation convention. | |
| Explanation | |
| =========== | |
| If the two tensors have an index in common, one contravariant | |
| and the other covariant, in their product the indices are summed | |
| Examples | |
| ======== | |
| >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, tensor_heads | |
| >>> Lorentz = TensorIndexType('Lorentz', dummy_name='L') | |
| >>> m0, m1, m2 = tensor_indices('m0,m1,m2', Lorentz) | |
| >>> g = Lorentz.metric | |
| >>> p, q = tensor_heads('p,q', [Lorentz]) | |
| >>> t1 = p(m0) | |
| >>> t2 = q(-m0) | |
| >>> t1*t2 | |
| p(L_0)*q(-L_0) | |
| """ | |
| return TensMul(self, other).doit() | |
| def __rmul__(self, other): | |
| return TensMul(other, self).doit() | |
| def __truediv__(self, other): | |
| other = _sympify(other) | |
| if isinstance(other, TensExpr): | |
| raise ValueError('cannot divide by a tensor') | |
| return TensMul(self, S.One/other).doit() | |
| def __rtruediv__(self, other): | |
| raise ValueError('cannot divide by a tensor') | |
| def __pow__(self, other): | |
| deprecate_data() | |
| with ignore_warnings(SymPyDeprecationWarning): | |
| if self.data is None: | |
| raise ValueError("No power without ndarray data.") | |
| from .array import tensorproduct, tensorcontraction | |
| free = self.free | |
| marray = self.data | |
| mdim = marray.rank() | |
| for metric in free: | |
| marray = tensorcontraction( | |
| tensorproduct( | |
| marray, | |
| metric[0].tensor_index_type.data, | |
| marray), | |
| (0, mdim), (mdim+1, mdim+2) | |
| ) | |
| return marray ** (other * S.Half) | |
| def __rpow__(self, other): | |
| raise NotImplementedError | |
| def nocoeff(self): | |
| raise NotImplementedError("abstract method") | |
| def coeff(self): | |
| raise NotImplementedError("abstract method") | |
| def get_indices(self): | |
| raise NotImplementedError("abstract method") | |
| def get_free_indices(self) -> list[TensorIndex]: | |
| raise NotImplementedError("abstract method") | |
| def _replace_indices(self, repl: dict[TensorIndex, TensorIndex]) -> TensExpr: | |
| raise NotImplementedError("abstract method") | |
| def fun_eval(self, *index_tuples): | |
| deprecate_fun_eval() | |
| return self.substitute_indices(*index_tuples) | |
| def get_matrix(self): | |
| """ | |
| DEPRECATED: do not use. | |
| Returns ndarray components data as a matrix, if components data are | |
| available and ndarray dimension does not exceed 2. | |
| """ | |
| from sympy.matrices.dense import Matrix | |
| deprecate_data() | |
| with ignore_warnings(SymPyDeprecationWarning): | |
| if 0 < self.rank <= 2: | |
| rows = self.data.shape[0] | |
| columns = self.data.shape[1] if self.rank == 2 else 1 | |
| if self.rank == 2: | |
| mat_list = [] * rows | |
| for i in range(rows): | |
| mat_list.append([]) | |
| for j in range(columns): | |
| mat_list[i].append(self[i, j]) | |
| else: | |
| mat_list = [None] * rows | |
| for i in range(rows): | |
| mat_list[i] = self[i] | |
| return Matrix(mat_list) | |
| else: | |
| raise NotImplementedError( | |
| "missing multidimensional reduction to matrix.") | |
| def _get_indices_permutation(indices1, indices2): | |
| return [indices1.index(i) for i in indices2] | |
| def expand(self, **hints): | |
| return _expand(self, **hints).doit() | |
| def _expand(self, **kwargs): | |
| return self | |
| def _get_free_indices_set(self): | |
| indset = set() | |
| for arg in self.args: | |
| if isinstance(arg, TensExpr): | |
| indset.update(arg._get_free_indices_set()) | |
| return indset | |
| def _get_dummy_indices_set(self): | |
| indset = set() | |
| for arg in self.args: | |
| if isinstance(arg, TensExpr): | |
| indset.update(arg._get_dummy_indices_set()) | |
| return indset | |
| def _get_indices_set(self): | |
| indset = set() | |
| for arg in self.args: | |
| if isinstance(arg, TensExpr): | |
| indset.update(arg._get_indices_set()) | |
| return indset | |
| def _iterate_dummy_indices(self): | |
| dummy_set = self._get_dummy_indices_set() | |
| def recursor(expr, pos): | |
| if isinstance(expr, TensorIndex): | |
| if expr in dummy_set: | |
| yield (expr, pos) | |
| elif isinstance(expr, (Tuple, TensExpr)): | |
| for p, arg in enumerate(expr.args): | |
| yield from recursor(arg, pos+(p,)) | |
| return recursor(self, ()) | |
| def _iterate_free_indices(self): | |
| free_set = self._get_free_indices_set() | |
| def recursor(expr, pos): | |
| if isinstance(expr, TensorIndex): | |
| if expr in free_set: | |
| yield (expr, pos) | |
| elif isinstance(expr, (Tuple, TensExpr)): | |
| for p, arg in enumerate(expr.args): | |
| yield from recursor(arg, pos+(p,)) | |
| return recursor(self, ()) | |
| def _iterate_indices(self): | |
| def recursor(expr, pos): | |
| if isinstance(expr, TensorIndex): | |
| yield (expr, pos) | |
| elif isinstance(expr, (Tuple, TensExpr)): | |
| for p, arg in enumerate(expr.args): | |
| yield from recursor(arg, pos+(p,)) | |
| return recursor(self, ()) | |
| def _contract_and_permute_with_metric(metric, array, pos, dim): | |
| # TODO: add possibility of metric after (spinors) | |
| from .array import tensorcontraction, tensorproduct, permutedims | |
| array = tensorcontraction(tensorproduct(metric, array), (1, 2+pos)) | |
| permu = list(range(dim)) | |
| permu[0], permu[pos] = permu[pos], permu[0] | |
| return permutedims(array, permu) | |
| def _match_indices_with_other_tensor(array, free_ind1, free_ind2, replacement_dict): | |
| from .array import permutedims | |
| index_types1 = [i.tensor_index_type for i in free_ind1] | |
| # Check if variance of indices needs to be fixed: | |
| pos2up = [] | |
| pos2down = [] | |
| free2remaining = free_ind2[:] | |
| for pos1, index1 in enumerate(free_ind1): | |
| if index1 in free2remaining: | |
| pos2 = free2remaining.index(index1) | |
| free2remaining[pos2] = None | |
| continue | |
| if -index1 in free2remaining: | |
| pos2 = free2remaining.index(-index1) | |
| free2remaining[pos2] = None | |
| free_ind2[pos2] = index1 | |
| if index1.is_up: | |
| pos2up.append(pos2) | |
| else: | |
| pos2down.append(pos2) | |
| else: | |
| index2 = free2remaining[pos1] | |
| if index2 is None: | |
| raise ValueError("incompatible indices: %s and %s" % (free_ind1, free_ind2)) | |
| free2remaining[pos1] = None | |
| free_ind2[pos1] = index1 | |
| if index1.is_up ^ index2.is_up: | |
| if index1.is_up: | |
| pos2up.append(pos1) | |
| else: | |
| pos2down.append(pos1) | |
| if len(set(free_ind1) & set(free_ind2)) < len(free_ind1): | |
| raise ValueError("incompatible indices: %s and %s" % (free_ind1, free_ind2)) | |
| # Raise indices: | |
| for pos in pos2up: | |
| index_type_pos = index_types1[pos] | |
| if index_type_pos not in replacement_dict: | |
| raise ValueError("No metric provided to lower index") | |
| metric = replacement_dict[index_type_pos] | |
| metric_inverse = _TensorDataLazyEvaluator.inverse_matrix(metric) | |
| array = TensExpr._contract_and_permute_with_metric(metric_inverse, array, pos, len(free_ind1)) | |
| # Lower indices: | |
| for pos in pos2down: | |
| index_type_pos = index_types1[pos] | |
| if index_type_pos not in replacement_dict: | |
| raise ValueError("No metric provided to lower index") | |
| metric = replacement_dict[index_type_pos] | |
| array = TensExpr._contract_and_permute_with_metric(metric, array, pos, len(free_ind1)) | |
| if free_ind1: | |
| permutation = TensExpr._get_indices_permutation(free_ind2, free_ind1) | |
| array = permutedims(array, permutation) | |
| if hasattr(array, "rank") and array.rank() == 0: | |
| array = array[()] | |
| return free_ind2, array | |
| def replace_with_arrays(self, replacement_dict, indices=None): | |
| """ | |
| Replace the tensorial expressions with arrays. The final array will | |
| correspond to the N-dimensional array with indices arranged according | |
| to ``indices``. | |
| Parameters | |
| ========== | |
| replacement_dict | |
| dictionary containing the replacement rules for tensors. | |
| indices | |
| the index order with respect to which the array is read. The | |
| original index order will be used if no value is passed. | |
| Examples | |
| ======== | |
| >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices | |
| >>> from sympy.tensor.tensor import TensorHead | |
| >>> from sympy import symbols, diag | |
| >>> L = TensorIndexType("L") | |
| >>> i, j = tensor_indices("i j", L) | |
| >>> A = TensorHead("A", [L]) | |
| >>> A(i).replace_with_arrays({A(i): [1, 2]}, [i]) | |
| [1, 2] | |
| Since 'indices' is optional, we can also call replace_with_arrays by | |
| this way if no specific index order is needed: | |
| >>> A(i).replace_with_arrays({A(i): [1, 2]}) | |
| [1, 2] | |
| >>> expr = A(i)*A(j) | |
| >>> expr.replace_with_arrays({A(i): [1, 2]}) | |
| [[1, 2], [2, 4]] | |
| For contractions, specify the metric of the ``TensorIndexType``, which | |
| in this case is ``L``, in its covariant form: | |
| >>> expr = A(i)*A(-i) | |
| >>> expr.replace_with_arrays({A(i): [1, 2], L: diag(1, -1)}) | |
| -3 | |
| Symmetrization of an array: | |
| >>> H = TensorHead("H", [L, L]) | |
| >>> a, b, c, d = symbols("a b c d") | |
| >>> expr = H(i, j)/2 + H(j, i)/2 | |
| >>> expr.replace_with_arrays({H(i, j): [[a, b], [c, d]]}) | |
| [[a, b/2 + c/2], [b/2 + c/2, d]] | |
| Anti-symmetrization of an array: | |
| >>> expr = H(i, j)/2 - H(j, i)/2 | |
| >>> repl = {H(i, j): [[a, b], [c, d]]} | |
| >>> expr.replace_with_arrays(repl) | |
| [[0, b/2 - c/2], [-b/2 + c/2, 0]] | |
| The same expression can be read as the transpose by inverting ``i`` and | |
| ``j``: | |
| >>> expr.replace_with_arrays(repl, [j, i]) | |
| [[0, -b/2 + c/2], [b/2 - c/2, 0]] | |
| """ | |
| from .array import Array | |
| indices = indices or [] | |
| remap = {k.args[0] if k.is_up else -k.args[0]: k for k in self.get_free_indices()} | |
| for i, index in enumerate(indices): | |
| if isinstance(index, (Symbol, Mul)): | |
| if index in remap: | |
| indices[i] = remap[index] | |
| else: | |
| indices[i] = -remap[-index] | |
| replacement_dict = {tensor: Array(array) for tensor, array in replacement_dict.items()} | |
| # Check dimensions of replaced arrays: | |
| for tensor, array in replacement_dict.items(): | |
| if isinstance(tensor, TensorIndexType): | |
| expected_shape = [tensor.dim for i in range(2)] | |
| else: | |
| expected_shape = [index_type.dim for index_type in tensor.index_types] | |
| if len(expected_shape) != array.rank() or (not all(dim1 == dim2 if | |
| dim1.is_number else True for dim1, dim2 in zip(expected_shape, | |
| array.shape))): | |
| raise ValueError("shapes for tensor %s expected to be %s, "\ | |
| "replacement array shape is %s" % (tensor, expected_shape, | |
| array.shape)) | |
| ret_indices, array = self._extract_data(replacement_dict) | |
| last_indices, array = self._match_indices_with_other_tensor(array, indices, ret_indices, replacement_dict) | |
| return array | |
| def _check_add_Sum(self, expr, index_symbols): | |
| from sympy.concrete.summations import Sum | |
| indices = self.get_indices() | |
| dum = self.dum | |
| sum_indices = [ (index_symbols[i], 0, | |
| indices[i].tensor_index_type.dim-1) for i, j in dum] | |
| if sum_indices: | |
| expr = Sum(expr, *sum_indices) | |
| return expr | |
| def _expand_partial_derivative(self): | |
| # simply delegate the _expand_partial_derivative() to | |
| # its arguments to expand a possibly found PartialDerivative | |
| return self.func(*[ | |
| a._expand_partial_derivative() | |
| if isinstance(a, TensExpr) else a | |
| for a in self.args]) | |
| class TensAdd(TensExpr, AssocOp): | |
| """ | |
| Sum of tensors. | |
| Parameters | |
| ========== | |
| free_args : list of the free indices | |
| Attributes | |
| ========== | |
| ``args`` : tuple of addends | |
| ``rank`` : rank of the tensor | |
| ``free_args`` : list of the free indices in sorted order | |
| Examples | |
| ======== | |
| >>> from sympy.tensor.tensor import TensorIndexType, tensor_heads, tensor_indices | |
| >>> Lorentz = TensorIndexType('Lorentz', dummy_name='L') | |
| >>> a, b = tensor_indices('a,b', Lorentz) | |
| >>> p, q = tensor_heads('p,q', [Lorentz]) | |
| >>> t = p(a) + q(a); t | |
| p(a) + q(a) | |
| Examples with components data added to the tensor expression: | |
| >>> from sympy import symbols, diag | |
| >>> x, y, z, t = symbols("x y z t") | |
| >>> repl = {} | |
| >>> repl[Lorentz] = diag(1, -1, -1, -1) | |
| >>> repl[p(a)] = [1, 2, 3, 4] | |
| >>> repl[q(a)] = [x, y, z, t] | |
| The following are: 2**2 - 3**2 - 2**2 - 7**2 ==> -58 | |
| >>> expr = p(a) + q(a) | |
| >>> expr.replace_with_arrays(repl, [a]) | |
| [x + 1, y + 2, z + 3, t + 4] | |
| """ | |
| def __new__(cls, *args, **kw_args): | |
| args = [_sympify(x) for x in args if x] | |
| args = TensAdd._tensAdd_flatten(args) | |
| args.sort(key=default_sort_key) | |
| if not args: | |
| return S.Zero | |
| if len(args) == 1: | |
| return args[0] | |
| return Basic.__new__(cls, *args, **kw_args) | |
| def coeff(self): | |
| return S.One | |
| def nocoeff(self): | |
| return self | |
| def get_free_indices(self) -> list[TensorIndex]: | |
| return self.free_indices | |
| def _replace_indices(self, repl: dict[TensorIndex, TensorIndex]) -> TensExpr: | |
| newargs = [arg._replace_indices(repl) if isinstance(arg, TensExpr) else arg for arg in self.args] | |
| return self.func(*newargs) | |
| def rank(self): | |
| if isinstance(self.args[0], TensExpr): | |
| return self.args[0].rank | |
| else: | |
| return 0 | |
| def free_args(self): | |
| if isinstance(self.args[0], TensExpr): | |
| return self.args[0].free_args | |
| else: | |
| return [] | |
| def free_indices(self): | |
| if isinstance(self.args[0], TensExpr): | |
| return self.args[0].get_free_indices() | |
| else: | |
| return set() | |
| def doit(self, **hints): | |
| deep = hints.get('deep', True) | |
| if deep: | |
| args = [arg.doit(**hints) for arg in self.args] | |
| else: | |
| args = self.args | |
| # if any of the args are zero (after doit), drop them. Otherwise, _tensAdd_check will complain about non-matching indices, even though the TensAdd is correctly formed. | |
| args = [arg for arg in args if arg != S.Zero] | |
| if len(args) == 0: | |
| return S.Zero | |
| elif len(args) == 1: | |
| return args[0] | |
| # now check that all addends have the same indices: | |
| TensAdd._tensAdd_check(args) | |
| # Collect terms appearing more than once, differing by their coefficients: | |
| args = TensAdd._tensAdd_collect_terms(args) | |
| # collect canonicalized terms | |
| def sort_key(t): | |
| if not isinstance(t, TensExpr): | |
| return [], [], [] | |
| if hasattr(t, "_index_structure") and hasattr(t, "components"): | |
| x = get_index_structure(t) | |
| return t.components, x.free, x.dum | |
| return [], [], [] | |
| args.sort(key=sort_key) | |
| if not args: | |
| return S.Zero | |
| # it there is only a component tensor return it | |
| if len(args) == 1: | |
| return args[0] | |
| obj = self.func(*args) | |
| return obj | |
| def _tensAdd_flatten(args): | |
| # flatten TensAdd, coerce terms which are not tensors to tensors | |
| a = [] | |
| for x in args: | |
| if isinstance(x, (Add, TensAdd)): | |
| a.extend(list(x.args)) | |
| else: | |
| a.append(x) | |
| args = [x for x in a if x.coeff] | |
| return args | |
| def _tensAdd_check(args): | |
| # check that all addends have the same free indices | |
| def get_indices_set(x: Expr) -> set[TensorIndex]: | |
| if isinstance(x, TensExpr): | |
| return set(x.get_free_indices()) | |
| return set() | |
| indices0 = get_indices_set(args[0]) | |
| list_indices = [get_indices_set(arg) for arg in args[1:]] | |
| if not all(x == indices0 for x in list_indices): | |
| raise ValueError('all tensors must have the same indices') | |
| def _tensAdd_collect_terms(args): | |
| # collect TensMul terms differing at most by their coefficient | |
| terms_dict = defaultdict(list) | |
| scalars = S.Zero | |
| if isinstance(args[0], TensExpr): | |
| free_indices = set(args[0].get_free_indices()) | |
| else: | |
| free_indices = set() | |
| for arg in args: | |
| if not isinstance(arg, TensExpr): | |
| if free_indices != set(): | |
| raise ValueError("wrong valence") | |
| scalars += arg | |
| continue | |
| if free_indices != set(arg.get_free_indices()): | |
| raise ValueError("wrong valence") | |
| # TODO: what is the part which is not a coeff? | |
| # needs an implementation similar to .as_coeff_Mul() | |
| terms_dict[arg.nocoeff].append(arg.coeff) | |
| new_args = [TensMul(Add(*coeff), t).doit() for t, coeff in terms_dict.items() if Add(*coeff) != 0] | |
| if isinstance(scalars, Add): | |
| new_args = list(scalars.args) + new_args | |
| elif scalars != 0: | |
| new_args = [scalars] + new_args | |
| return new_args | |
| def get_indices(self): | |
| indices = [] | |
| for arg in self.args: | |
| indices.extend([i for i in get_indices(arg) if i not in indices]) | |
| return indices | |
| def _expand(self, **hints): | |
| return TensAdd(*[_expand(i, **hints) for i in self.args]) | |
| def __call__(self, *indices): | |
| deprecate_call() | |
| free_args = self.free_args | |
| indices = list(indices) | |
| if [x.tensor_index_type for x in indices] != [x.tensor_index_type for x in free_args]: | |
| raise ValueError('incompatible types') | |
| if indices == free_args: | |
| return self | |
| index_tuples = list(zip(free_args, indices)) | |
| a = [x.func(*x.substitute_indices(*index_tuples).args) for x in self.args] | |
| res = TensAdd(*a).doit() | |
| return res | |
| def canon_bp(self): | |
| """ | |
| Canonicalize using the Butler-Portugal algorithm for canonicalization | |
| under monoterm symmetries. | |
| """ | |
| expr = self.expand() | |
| args = [canon_bp(x) for x in expr.args] | |
| res = TensAdd(*args).doit() | |
| return res | |
| def equals(self, other): | |
| other = _sympify(other) | |
| if isinstance(other, TensMul) and other.coeff == 0: | |
| return all(x.coeff == 0 for x in self.args) | |
| if isinstance(other, TensExpr): | |
| if self.rank != other.rank: | |
| return False | |
| if isinstance(other, TensAdd): | |
| if set(self.args) != set(other.args): | |
| return False | |
| else: | |
| return True | |
| t = self - other | |
| if not isinstance(t, TensExpr): | |
| return t == 0 | |
| else: | |
| if isinstance(t, TensMul): | |
| return t.coeff == 0 | |
| else: | |
| return all(x.coeff == 0 for x in t.args) | |
| def __getitem__(self, item): | |
| deprecate_data() | |
| with ignore_warnings(SymPyDeprecationWarning): | |
| return self.data[item] | |
| def contract_delta(self, delta): | |
| args = [x.contract_delta(delta) for x in self.args] | |
| t = TensAdd(*args).doit() | |
| return canon_bp(t) | |
| def contract_metric(self, g): | |
| """ | |
| Raise or lower indices with the metric ``g``. | |
| Parameters | |
| ========== | |
| g : metric | |
| contract_all : if True, eliminate all ``g`` which are contracted | |
| Notes | |
| ===== | |
| see the ``TensorIndexType`` docstring for the contraction conventions | |
| """ | |
| args = [contract_metric(x, g) for x in self.args] | |
| t = TensAdd(*args).doit() | |
| return canon_bp(t) | |
| def substitute_indices(self, *index_tuples): | |
| new_args = [] | |
| for arg in self.args: | |
| if isinstance(arg, TensExpr): | |
| arg = arg.substitute_indices(*index_tuples) | |
| new_args.append(arg) | |
| return TensAdd(*new_args).doit() | |
| def _print(self): | |
| a = [] | |
| args = self.args | |
| for x in args: | |
| a.append(str(x)) | |
| s = ' + '.join(a) | |
| s = s.replace('+ -', '- ') | |
| return s | |
| def _extract_data(self, replacement_dict): | |
| from sympy.tensor.array import Array, permutedims | |
| args_indices, arrays = zip(*[ | |
| arg._extract_data(replacement_dict) if | |
| isinstance(arg, TensExpr) else ([], arg) for arg in self.args | |
| ]) | |
| arrays = [Array(i) for i in arrays] | |
| ref_indices = args_indices[0] | |
| for i in range(1, len(args_indices)): | |
| indices = args_indices[i] | |
| array = arrays[i] | |
| permutation = TensMul._get_indices_permutation(indices, ref_indices) | |
| arrays[i] = permutedims(array, permutation) | |
| return ref_indices, sum(arrays, Array.zeros(*array.shape)) | |
| def data(self): | |
| deprecate_data() | |
| with ignore_warnings(SymPyDeprecationWarning): | |
| return _tensor_data_substitution_dict[self.expand()] | |
| def data(self, data): | |
| deprecate_data() | |
| with ignore_warnings(SymPyDeprecationWarning): | |
| _tensor_data_substitution_dict[self] = data | |
| def data(self): | |
| deprecate_data() | |
| with ignore_warnings(SymPyDeprecationWarning): | |
| if self in _tensor_data_substitution_dict: | |
| del _tensor_data_substitution_dict[self] | |
| def __iter__(self): | |
| deprecate_data() | |
| if not self.data: | |
| raise ValueError("No iteration on abstract tensors") | |
| return self.data.flatten().__iter__() | |
| def _eval_rewrite_as_Indexed(self, *args, **kwargs): | |
| return Add.fromiter(args) | |
| def _eval_partial_derivative(self, s): | |
| # Evaluation like Add | |
| list_addends = [] | |
| for a in self.args: | |
| if isinstance(a, TensExpr): | |
| list_addends.append(a._eval_partial_derivative(s)) | |
| # do not call diff if s is no symbol | |
| elif s._diff_wrt: | |
| list_addends.append(a._eval_derivative(s)) | |
| return self.func(*list_addends) | |
| class Tensor(TensExpr): | |
| """ | |
| Base tensor class, i.e. this represents a tensor, the single unit to be | |
| put into an expression. | |
| Explanation | |
| =========== | |
| This object is usually created from a ``TensorHead``, by attaching indices | |
| to it. Indices preceded by a minus sign are considered contravariant, | |
| otherwise covariant. | |
| Examples | |
| ======== | |
| >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, TensorHead | |
| >>> Lorentz = TensorIndexType("Lorentz", dummy_name="L") | |
| >>> mu, nu = tensor_indices('mu nu', Lorentz) | |
| >>> A = TensorHead("A", [Lorentz, Lorentz]) | |
| >>> A(mu, -nu) | |
| A(mu, -nu) | |
| >>> A(mu, -mu) | |
| A(L_0, -L_0) | |
| It is also possible to use symbols instead of inidices (appropriate indices | |
| are then generated automatically). | |
| >>> from sympy import Symbol | |
| >>> x = Symbol('x') | |
| >>> A(x, mu) | |
| A(x, mu) | |
| >>> A(x, -x) | |
| A(L_0, -L_0) | |
| """ | |
| is_commutative = False | |
| _index_structure = None # type: _IndexStructure | |
| args: tuple[TensorHead, Tuple] | |
| def __new__(cls, tensor_head, indices, *, is_canon_bp=False, **kw_args): | |
| indices = cls._parse_indices(tensor_head, indices) | |
| obj = Basic.__new__(cls, tensor_head, Tuple(*indices), **kw_args) | |
| obj._index_structure = _IndexStructure.from_indices(*indices) | |
| obj._free = obj._index_structure.free[:] | |
| obj._dum = obj._index_structure.dum[:] | |
| obj._ext_rank = obj._index_structure._ext_rank | |
| obj._coeff = S.One | |
| obj._nocoeff = obj | |
| obj._component = tensor_head | |
| obj._components = [tensor_head] | |
| if tensor_head.rank != len(indices): | |
| raise ValueError("wrong number of indices") | |
| obj.is_canon_bp = is_canon_bp | |
| obj._index_map = Tensor._build_index_map(indices, obj._index_structure) | |
| return obj | |
| def free(self): | |
| return self._free | |
| def dum(self): | |
| return self._dum | |
| def ext_rank(self): | |
| return self._ext_rank | |
| def coeff(self): | |
| return self._coeff | |
| def nocoeff(self): | |
| return self._nocoeff | |
| def component(self): | |
| return self._component | |
| def components(self): | |
| return self._components | |
| def head(self): | |
| return self.args[0] | |
| def indices(self): | |
| return self.args[1] | |
| def free_indices(self): | |
| return set(self._index_structure.get_free_indices()) | |
| def index_types(self): | |
| return self.head.index_types | |
| def rank(self): | |
| return len(self.free_indices) | |
| def _build_index_map(indices, index_structure): | |
| index_map = {} | |
| for idx in indices: | |
| index_map[idx] = (indices.index(idx),) | |
| return index_map | |
| def doit(self, **hints): | |
| args, indices, free, dum = TensMul._tensMul_contract_indices([self]) | |
| return args[0] | |
| def _parse_indices(tensor_head, indices): | |
| if not isinstance(indices, (tuple, list, Tuple)): | |
| raise TypeError("indices should be an array, got %s" % type(indices)) | |
| indices = list(indices) | |
| for i, index in enumerate(indices): | |
| if isinstance(index, Symbol): | |
| indices[i] = TensorIndex(index, tensor_head.index_types[i], True) | |
| elif isinstance(index, Mul): | |
| c, e = index.as_coeff_Mul() | |
| if c == -1 and isinstance(e, Symbol): | |
| indices[i] = TensorIndex(e, tensor_head.index_types[i], False) | |
| else: | |
| raise ValueError("index not understood: %s" % index) | |
| elif not isinstance(index, TensorIndex): | |
| raise TypeError("wrong type for index: %s is %s" % (index, type(index))) | |
| return indices | |
| def _set_new_index_structure(self, im, is_canon_bp=False): | |
| indices = im.get_indices() | |
| return self._set_indices(*indices, is_canon_bp=is_canon_bp) | |
| def _set_indices(self, *indices, is_canon_bp=False, **kw_args): | |
| if len(indices) != self.ext_rank: | |
| raise ValueError("indices length mismatch") | |
| return self.func(self.args[0], indices, is_canon_bp=is_canon_bp).doit() | |
| def _get_free_indices_set(self): | |
| return {i[0] for i in self._index_structure.free} | |
| def _get_dummy_indices_set(self): | |
| dummy_pos = set(itertools.chain(*self._index_structure.dum)) | |
| return {idx for i, idx in enumerate(self.args[1]) if i in dummy_pos} | |
| def _get_indices_set(self): | |
| return set(self.args[1].args) | |
| def free_in_args(self): | |
| return [(ind, pos, 0) for ind, pos in self.free] | |
| def dum_in_args(self): | |
| return [(p1, p2, 0, 0) for p1, p2 in self.dum] | |
| def free_args(self): | |
| return sorted([x[0] for x in self.free]) | |
| def commutes_with(self, other): | |
| """ | |
| :param other: | |
| :return: | |
| 0 commute | |
| 1 anticommute | |
| None neither commute nor anticommute | |
| """ | |
| if not isinstance(other, TensExpr): | |
| return 0 | |
| elif isinstance(other, Tensor): | |
| return self.component.commutes_with(other.component) | |
| return NotImplementedError | |
| def perm2tensor(self, g, is_canon_bp=False): | |
| """ | |
| Returns the tensor corresponding to the permutation ``g``. | |
| For further details, see the method in ``TIDS`` with the same name. | |
| """ | |
| return perm2tensor(self, g, is_canon_bp) | |
| def canon_bp(self): | |
| if self.is_canon_bp: | |
| return self | |
| expr = self.expand() | |
| g, dummies, msym = expr._index_structure.indices_canon_args() | |
| v = components_canon_args([expr.component]) | |
| can = canonicalize(g, dummies, msym, *v) | |
| if can == 0: | |
| return S.Zero | |
| tensor = self.perm2tensor(can, True) | |
| return tensor | |
| def split(self): | |
| return [self] | |
| def _expand(self, **kwargs): | |
| return self | |
| def sorted_components(self): | |
| return self | |
| def get_indices(self) -> list[TensorIndex]: | |
| """ | |
| Get a list of indices, corresponding to those of the tensor. | |
| """ | |
| return list(self.args[1]) | |
| def get_free_indices(self) -> list[TensorIndex]: | |
| """ | |
| Get a list of free indices, corresponding to those of the tensor. | |
| """ | |
| return self._index_structure.get_free_indices() | |
| def _replace_indices(self, repl: dict[TensorIndex, TensorIndex]) -> TensExpr: | |
| # TODO: this could be optimized by only swapping the indices | |
| # instead of visiting the whole expression tree: | |
| return self.xreplace(repl) | |
| def as_base_exp(self): | |
| return self, S.One | |
| def substitute_indices(self, *index_tuples): | |
| """ | |
| Return a tensor with free indices substituted according to ``index_tuples``. | |
| ``index_types`` list of tuples ``(old_index, new_index)``. | |
| Examples | |
| ======== | |
| >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, tensor_heads, TensorSymmetry | |
| >>> Lorentz = TensorIndexType('Lorentz', dummy_name='L') | |
| >>> i, j, k, l = tensor_indices('i,j,k,l', Lorentz) | |
| >>> A, B = tensor_heads('A,B', [Lorentz]*2, TensorSymmetry.fully_symmetric(2)) | |
| >>> t = A(i, k)*B(-k, -j); t | |
| A(i, L_0)*B(-L_0, -j) | |
| >>> t.substitute_indices((i, k),(-j, l)) | |
| A(k, L_0)*B(-L_0, l) | |
| """ | |
| indices = [] | |
| for index in self.indices: | |
| for ind_old, ind_new in index_tuples: | |
| if (index.name == ind_old.name and index.tensor_index_type == | |
| ind_old.tensor_index_type): | |
| if index.is_up == ind_old.is_up: | |
| indices.append(ind_new) | |
| else: | |
| indices.append(-ind_new) | |
| break | |
| else: | |
| indices.append(index) | |
| return self.head(*indices) | |
| def _get_symmetrized_forms(self): | |
| """ | |
| Return a list giving all possible permutations of self that are allowed by its symmetries. | |
| """ | |
| comp = self.component | |
| gens = comp.symmetry.generators | |
| rank = comp.rank | |
| old_perms = None | |
| new_perms = {self} | |
| while new_perms != old_perms: | |
| old_perms = new_perms.copy() | |
| for tens in old_perms: | |
| for gen in gens: | |
| inds = tens.get_indices() | |
| per = [gen.apply(i) for i in range(0,rank)] | |
| sign = (-1)**(gen.apply(rank) - rank) | |
| ind_map = dict(zip(inds, [inds[i] for i in per])) | |
| new_perms.add( sign * tens._replace_indices(ind_map) ) | |
| return new_perms | |
| def matches(self, expr, repl_dict=None, old=False): | |
| expr = sympify(expr) | |
| if repl_dict is None: | |
| repl_dict = {} | |
| else: | |
| repl_dict = repl_dict.copy() | |
| #simple checks | |
| if self == expr: | |
| return repl_dict | |
| if not isinstance(expr, Tensor): | |
| return None | |
| if self.head != expr.head: | |
| return None | |
| #Now consider all index symmetries of expr, and see if any of them allow a match. | |
| for new_expr in expr._get_symmetrized_forms(): | |
| m = self._matches(new_expr, repl_dict, old=old) | |
| if m is not None: | |
| repl_dict.update(m) | |
| return repl_dict | |
| return None | |
| def _matches(self, expr, repl_dict=None, old=False): | |
| """ | |
| This does not account for index symmetries of expr | |
| """ | |
| expr = sympify(expr) | |
| if repl_dict is None: | |
| repl_dict = {} | |
| else: | |
| repl_dict = repl_dict.copy() | |
| #simple checks | |
| if self == expr: | |
| return repl_dict | |
| if not isinstance(expr, Tensor): | |
| return None | |
| if self.head != expr.head: | |
| return None | |
| s_indices = self.get_indices() | |
| e_indices = expr.get_indices() | |
| if len(s_indices) != len(e_indices): | |
| return None | |
| for i in range(len(s_indices)): | |
| s_ind = s_indices[i] | |
| m = s_ind.matches(e_indices[i]) | |
| if m is None: | |
| return None | |
| elif -s_ind in repl_dict.keys() and -repl_dict[-s_ind] != m[s_ind]: | |
| return None | |
| else: | |
| repl_dict.update(m) | |
| return repl_dict | |
| def __call__(self, *indices): | |
| deprecate_call() | |
| free_args = self.free_args | |
| indices = list(indices) | |
| if [x.tensor_index_type for x in indices] != [x.tensor_index_type for x in free_args]: | |
| raise ValueError('incompatible types') | |
| if indices == free_args: | |
| return self | |
| t = self.substitute_indices(*list(zip(free_args, indices))) | |
| # object is rebuilt in order to make sure that all contracted indices | |
| # get recognized as dummies, but only if there are contracted indices. | |
| if len({i if i.is_up else -i for i in indices}) != len(indices): | |
| return t.func(*t.args) | |
| return t | |
| # TODO: put this into TensExpr? | |
| def __iter__(self): | |
| deprecate_data() | |
| with ignore_warnings(SymPyDeprecationWarning): | |
| return self.data.__iter__() | |
| # TODO: put this into TensExpr? | |
| def __getitem__(self, item): | |
| deprecate_data() | |
| with ignore_warnings(SymPyDeprecationWarning): | |
| return self.data[item] | |
| def _extract_data(self, replacement_dict): | |
| from .array import Array | |
| for k, v in replacement_dict.items(): | |
| if isinstance(k, Tensor) and k.args[0] == self.args[0]: | |
| other = k | |
| array = v | |
| break | |
| else: | |
| raise ValueError("%s not found in %s" % (self, replacement_dict)) | |
| # TODO: inefficient, this should be done at root level only: | |
| replacement_dict = {k: Array(v) for k, v in replacement_dict.items()} | |
| array = Array(array) | |
| dum1 = self.dum | |
| dum2 = other.dum | |
| if len(dum2) > 0: | |
| for pair in dum2: | |
| # allow `dum2` if the contained values are also in `dum1`. | |
| if pair not in dum1: | |
| raise NotImplementedError("%s with contractions is not implemented" % other) | |
| # Remove elements in `dum2` from `dum1`: | |
| dum1 = [pair for pair in dum1 if pair not in dum2] | |
| if len(dum1) > 0: | |
| indices1 = self.get_indices() | |
| indices2 = other.get_indices() | |
| repl = {} | |
| for p1, p2 in dum1: | |
| repl[indices2[p2]] = -indices2[p1] | |
| for pos in (p1, p2): | |
| if indices1[pos].is_up ^ indices2[pos].is_up: | |
| metric = replacement_dict[indices1[pos].tensor_index_type] | |
| if indices1[pos].is_up: | |
| metric = _TensorDataLazyEvaluator.inverse_matrix(metric) | |
| array = self._contract_and_permute_with_metric(metric, array, pos, len(indices2)) | |
| other = other.xreplace(repl).doit() | |
| array = _TensorDataLazyEvaluator.data_contract_dum([array], dum1, len(indices2)) | |
| free_ind1 = self.get_free_indices() | |
| free_ind2 = other.get_free_indices() | |
| return self._match_indices_with_other_tensor(array, free_ind1, free_ind2, replacement_dict) | |
| def data(self): | |
| deprecate_data() | |
| with ignore_warnings(SymPyDeprecationWarning): | |
| return _tensor_data_substitution_dict[self] | |
| def data(self, data): | |
| deprecate_data() | |
| # TODO: check data compatibility with properties of tensor. | |
| with ignore_warnings(SymPyDeprecationWarning): | |
| _tensor_data_substitution_dict[self] = data | |
| def data(self): | |
| deprecate_data() | |
| with ignore_warnings(SymPyDeprecationWarning): | |
| if self in _tensor_data_substitution_dict: | |
| del _tensor_data_substitution_dict[self] | |
| if self.metric in _tensor_data_substitution_dict: | |
| del _tensor_data_substitution_dict[self.metric] | |
| def _print(self): | |
| indices = [str(ind) for ind in self.indices] | |
| component = self.component | |
| if component.rank > 0: | |
| return ('%s(%s)' % (component.name, ', '.join(indices))) | |
| else: | |
| return ('%s' % component.name) | |
| def equals(self, other): | |
| if other == 0: | |
| return self.coeff == 0 | |
| other = _sympify(other) | |
| if not isinstance(other, TensExpr): | |
| assert not self.components | |
| return S.One == other | |
| def _get_compar_comp(self): | |
| t = self.canon_bp() | |
| r = (t.coeff, tuple(t.components), \ | |
| tuple(sorted(t.free)), tuple(sorted(t.dum))) | |
| return r | |
| return _get_compar_comp(self) == _get_compar_comp(other) | |
| def contract_metric(self, g): | |
| # if metric is not the same, ignore this step: | |
| if self.component != g: | |
| return self | |
| # in case there are free components, do not perform anything: | |
| if len(self.free) != 0: | |
| return self | |
| #antisym = g.index_types[0].metric_antisym | |
| if g.symmetry == TensorSymmetry.fully_symmetric(-2): | |
| antisym = 1 | |
| elif g.symmetry == TensorSymmetry.fully_symmetric(2): | |
| antisym = 0 | |
| elif g.symmetry == TensorSymmetry.no_symmetry(2): | |
| antisym = None | |
| else: | |
| raise NotImplementedError | |
| sign = S.One | |
| typ = g.index_types[0] | |
| if not antisym: | |
| # g(i, -i) | |
| sign = sign*typ.dim | |
| else: | |
| # g(i, -i) | |
| sign = sign*typ.dim | |
| dp0, dp1 = self.dum[0] | |
| if dp0 < dp1: | |
| # g(i, -i) = -D with antisymmetric metric | |
| sign = -sign | |
| return sign | |
| def contract_delta(self, metric): | |
| return self.contract_metric(metric) | |
| def _eval_rewrite_as_Indexed(self, tens, indices, **kwargs): | |
| from sympy.tensor.indexed import Indexed | |
| # TODO: replace .args[0] with .name: | |
| index_symbols = [i.args[0] for i in self.get_indices()] | |
| expr = Indexed(tens.args[0], *index_symbols) | |
| return self._check_add_Sum(expr, index_symbols) | |
| def _eval_partial_derivative(self, s): # type: (Tensor) -> Expr | |
| if not isinstance(s, Tensor): | |
| return S.Zero | |
| else: | |
| # @a_i/@a_k = delta_i^k | |
| # @a_i/@a^k = g_ij delta^j_k | |
| # @a^i/@a^k = delta^i_k | |
| # @a^i/@a_k = g^ij delta_j^k | |
| # TODO: if there is no metric present, the derivative should be zero? | |
| if self.head != s.head: | |
| return S.Zero | |
| # if heads are the same, provide delta and/or metric products | |
| # for every free index pair in the appropriate tensor | |
| # assumed that the free indices are in proper order | |
| # A contravariante index in the derivative becomes covariant | |
| # after performing the derivative and vice versa | |
| kronecker_delta_list = [1] | |
| # not guarantee a correct index order | |
| for (count, (iself, iother)) in enumerate(zip(self.get_free_indices(), s.get_free_indices())): | |
| if iself.tensor_index_type != iother.tensor_index_type: | |
| raise ValueError("index types not compatible") | |
| else: | |
| tensor_index_type = iself.tensor_index_type | |
| tensor_metric = tensor_index_type.metric | |
| dummy = TensorIndex("d_" + str(count), tensor_index_type, | |
| is_up=iself.is_up) | |
| if iself.is_up == iother.is_up: | |
| kroneckerdelta = tensor_index_type.delta(iself, -iother) | |
| else: | |
| kroneckerdelta = ( | |
| TensMul(tensor_metric(iself, dummy), | |
| tensor_index_type.delta(-dummy, -iother)) | |
| ) | |
| kronecker_delta_list.append(kroneckerdelta) | |
| return TensMul.fromiter(kronecker_delta_list).doit() | |
| # doit necessary to rename dummy indices accordingly | |
| class TensMul(TensExpr, AssocOp): | |
| """ | |
| Product of tensors. | |
| Parameters | |
| ========== | |
| coeff : SymPy coefficient of the tensor | |
| args | |
| Attributes | |
| ========== | |
| ``components`` : list of ``TensorHead`` of the component tensors | |
| ``types`` : list of nonrepeated ``TensorIndexType`` | |
| ``free`` : list of ``(ind, ipos, icomp)``, see Notes | |
| ``dum`` : list of ``(ipos1, ipos2, icomp1, icomp2)``, see Notes | |
| ``ext_rank`` : rank of the tensor counting the dummy indices | |
| ``rank`` : rank of the tensor | |
| ``coeff`` : SymPy coefficient of the tensor | |
| ``free_args`` : list of the free indices in sorted order | |
| ``is_canon_bp`` : ``True`` if the tensor in in canonical form | |
| Notes | |
| ===== | |
| ``args[0]`` list of ``TensorHead`` of the component tensors. | |
| ``args[1]`` list of ``(ind, ipos, icomp)`` | |
| where ``ind`` is a free index, ``ipos`` is the slot position | |
| of ``ind`` in the ``icomp``-th component tensor. | |
| ``args[2]`` list of tuples representing dummy indices. | |
| ``(ipos1, ipos2, icomp1, icomp2)`` indicates that the contravariant | |
| dummy index is the ``ipos1``-th slot position in the ``icomp1``-th | |
| component tensor; the corresponding covariant index is | |
| in the ``ipos2`` slot position in the ``icomp2``-th component tensor. | |
| """ | |
| identity = S.One | |
| _index_structure = None # type: _IndexStructure | |
| def __new__(cls, *args, **kw_args): | |
| is_canon_bp = kw_args.get('is_canon_bp', False) | |
| args = list(map(_sympify, args)) | |
| """ | |
| If the internal dummy indices in one arg conflict with the free indices | |
| of the remaining args, we need to rename those internal dummy indices. | |
| """ | |
| free = [get_free_indices(arg) for arg in args] | |
| free = set(itertools.chain(*free)) #flatten free | |
| newargs = [] | |
| for arg in args: | |
| dum_this = set(get_dummy_indices(arg)) | |
| dum_other = [get_dummy_indices(a) for a in newargs] | |
| dum_other = set(itertools.chain(*dum_other)) #flatten dum_other | |
| free_this = set(get_free_indices(arg)) | |
| if len(dum_this.intersection(free)) > 0: | |
| exclude = free_this.union(free, dum_other) | |
| newarg = TensMul._dedupe_indices(arg, exclude) | |
| else: | |
| newarg = arg | |
| newargs.append(newarg) | |
| args = newargs | |
| # Flatten: | |
| args = [i for arg in args for i in (arg.args if isinstance(arg, (TensMul, Mul)) else [arg])] | |
| args, indices, free, dum = TensMul._tensMul_contract_indices(args, replace_indices=False) | |
| # Data for indices: | |
| index_types = [i.tensor_index_type for i in indices] | |
| index_structure = _IndexStructure(free, dum, index_types, indices, canon_bp=is_canon_bp) | |
| obj = TensExpr.__new__(cls, *args) | |
| obj._indices = indices | |
| obj._index_types = index_types[:] | |
| obj._index_structure = index_structure | |
| obj._free = index_structure.free[:] | |
| obj._dum = index_structure.dum[:] | |
| obj._free_indices = {x[0] for x in obj.free} | |
| obj._rank = len(obj.free) | |
| obj._ext_rank = len(obj._index_structure.free) + 2*len(obj._index_structure.dum) | |
| obj._coeff = S.One | |
| obj._is_canon_bp = is_canon_bp | |
| return obj | |
| index_types = property(lambda self: self._index_types) | |
| free = property(lambda self: self._free) | |
| dum = property(lambda self: self._dum) | |
| free_indices = property(lambda self: self._free_indices) | |
| rank = property(lambda self: self._rank) | |
| ext_rank = property(lambda self: self._ext_rank) | |
| def _indices_to_free_dum(args_indices): | |
| free2pos1 = {} | |
| free2pos2 = {} | |
| dummy_data = [] | |
| indices = [] | |
| # Notation for positions (to better understand the code): | |
| # `pos1`: position in the `args`. | |
| # `pos2`: position in the indices. | |
| # Example: | |
| # A(i, j)*B(k, m, n)*C(p) | |
| # `pos1` of `n` is 1 because it's in `B` (second `args` of TensMul). | |
| # `pos2` of `n` is 4 because it's the fifth overall index. | |
| # Counter for the index position wrt the whole expression: | |
| pos2 = 0 | |
| for pos1, arg_indices in enumerate(args_indices): | |
| for index in arg_indices: | |
| if not isinstance(index, TensorIndex): | |
| raise TypeError("expected TensorIndex") | |
| if -index in free2pos1: | |
| # Dummy index detected: | |
| other_pos1 = free2pos1.pop(-index) | |
| other_pos2 = free2pos2.pop(-index) | |
| if index.is_up: | |
| dummy_data.append((index, pos1, other_pos1, pos2, other_pos2)) | |
| else: | |
| dummy_data.append((-index, other_pos1, pos1, other_pos2, pos2)) | |
| indices.append(index) | |
| elif index in free2pos1: | |
| raise ValueError("Repeated index: %s" % index) | |
| else: | |
| free2pos1[index] = pos1 | |
| free2pos2[index] = pos2 | |
| indices.append(index) | |
| pos2 += 1 | |
| free = list(free2pos2.items()) | |
| free_names = [i.name for i in free2pos2.keys()] | |
| dummy_data.sort(key=lambda x: x[3]) | |
| return indices, free, free_names, dummy_data | |
| def _dummy_data_to_dum(dummy_data): | |
| return [(p2a, p2b) for (i, p1a, p1b, p2a, p2b) in dummy_data] | |
| def _tensMul_contract_indices(args, replace_indices=True): | |
| replacements = [{} for _ in args] | |
| #_index_order = all(_has_index_order(arg) for arg in args) | |
| args_indices = [get_indices(arg) for arg in args] | |
| indices, free, free_names, dummy_data = TensMul._indices_to_free_dum(args_indices) | |
| cdt = defaultdict(int) | |
| def dummy_name_gen(tensor_index_type): | |
| nd = str(cdt[tensor_index_type]) | |
| cdt[tensor_index_type] += 1 | |
| return tensor_index_type.dummy_name + '_' + nd | |
| if replace_indices: | |
| for old_index, pos1cov, pos1contra, pos2cov, pos2contra in dummy_data: | |
| index_type = old_index.tensor_index_type | |
| while True: | |
| dummy_name = dummy_name_gen(index_type) | |
| if dummy_name not in free_names: | |
| break | |
| dummy = TensorIndex(dummy_name, index_type, True) | |
| replacements[pos1cov][old_index] = dummy | |
| replacements[pos1contra][-old_index] = -dummy | |
| indices[pos2cov] = dummy | |
| indices[pos2contra] = -dummy | |
| args = [ | |
| arg._replace_indices(repl) if isinstance(arg, TensExpr) else arg | |
| for arg, repl in zip(args, replacements)] | |
| dum = TensMul._dummy_data_to_dum(dummy_data) | |
| return args, indices, free, dum | |
| def _get_components_from_args(args): | |
| """ | |
| Get a list of ``Tensor`` objects having the same ``TIDS`` if multiplied | |
| by one another. | |
| """ | |
| components = [] | |
| for arg in args: | |
| if not isinstance(arg, TensExpr): | |
| continue | |
| if isinstance(arg, TensAdd): | |
| continue | |
| components.extend(arg.components) | |
| return components | |
| def _rebuild_tensors_list(args, index_structure): | |
| indices = index_structure.get_indices() | |
| #tensors = [None for i in components] # pre-allocate list | |
| ind_pos = 0 | |
| for i, arg in enumerate(args): | |
| if not isinstance(arg, TensExpr): | |
| continue | |
| prev_pos = ind_pos | |
| ind_pos += arg.ext_rank | |
| args[i] = Tensor(arg.component, indices[prev_pos:ind_pos]) | |
| def doit(self, **hints): | |
| is_canon_bp = self._is_canon_bp | |
| deep = hints.get('deep', True) | |
| if deep: | |
| args = [arg.doit(**hints) for arg in self.args] | |
| """ | |
| There may now be conflicts between dummy indices of different args | |
| (each arg's doit method does not have any information about which | |
| dummy indices are already used in the other args), so we | |
| deduplicate them. | |
| """ | |
| rule = dict(zip(self.args, args)) | |
| rule = self._dedupe_indices_in_rule(rule) | |
| args = [rule[a] for a in self.args] | |
| else: | |
| args = self.args | |
| args = [arg for arg in args if arg != self.identity] | |
| # Extract non-tensor coefficients: | |
| coeff = reduce(lambda a, b: a*b, [arg for arg in args if not isinstance(arg, TensExpr)], S.One) | |
| args = [arg for arg in args if isinstance(arg, TensExpr)] | |
| if len(args) == 0: | |
| return coeff | |
| if coeff != self.identity: | |
| args = [coeff] + args | |
| if coeff == 0: | |
| return S.Zero | |
| if len(args) == 1: | |
| return args[0] | |
| args, indices, free, dum = TensMul._tensMul_contract_indices(args) | |
| # Data for indices: | |
| index_types = [i.tensor_index_type for i in indices] | |
| index_structure = _IndexStructure(free, dum, index_types, indices, canon_bp=is_canon_bp) | |
| obj = self.func(*args) | |
| obj._index_types = index_types | |
| obj._index_structure = index_structure | |
| obj._ext_rank = len(obj._index_structure.free) + 2*len(obj._index_structure.dum) | |
| obj._coeff = coeff | |
| obj._is_canon_bp = is_canon_bp | |
| return obj | |
| # TODO: this method should be private | |
| # TODO: should this method be renamed _from_components_free_dum ? | |
| def from_data(coeff, components, free, dum, **kw_args): | |
| return TensMul(coeff, *TensMul._get_tensors_from_components_free_dum(components, free, dum), **kw_args).doit() | |
| def _get_tensors_from_components_free_dum(components, free, dum): | |
| """ | |
| Get a list of ``Tensor`` objects by distributing ``free`` and ``dum`` indices on the ``components``. | |
| """ | |
| index_structure = _IndexStructure.from_components_free_dum(components, free, dum) | |
| indices = index_structure.get_indices() | |
| tensors = [None for i in components] # pre-allocate list | |
| # distribute indices on components to build a list of tensors: | |
| ind_pos = 0 | |
| for i, component in enumerate(components): | |
| prev_pos = ind_pos | |
| ind_pos += component.rank | |
| tensors[i] = Tensor(component, indices[prev_pos:ind_pos]) | |
| return tensors | |
| def _get_free_indices_set(self): | |
| return {i[0] for i in self.free} | |
| def _get_dummy_indices_set(self): | |
| dummy_pos = set(itertools.chain(*self.dum)) | |
| return {idx for i, idx in enumerate(self._index_structure.get_indices()) if i in dummy_pos} | |
| def _get_position_offset_for_indices(self): | |
| arg_offset = [None for i in range(self.ext_rank)] | |
| counter = 0 | |
| for arg in self.args: | |
| if not isinstance(arg, TensExpr): | |
| continue | |
| for j in range(arg.ext_rank): | |
| arg_offset[j + counter] = counter | |
| counter += arg.ext_rank | |
| return arg_offset | |
| def free_args(self): | |
| return sorted([x[0] for x in self.free]) | |
| def components(self): | |
| return self._get_components_from_args(self.args) | |
| def free_in_args(self): | |
| arg_offset = self._get_position_offset_for_indices() | |
| argpos = self._get_indices_to_args_pos() | |
| return [(ind, pos-arg_offset[pos], argpos[pos]) for (ind, pos) in self.free] | |
| def coeff(self): | |
| # return Mul.fromiter([c for c in self.args if not isinstance(c, TensExpr)]) | |
| return self._coeff | |
| def nocoeff(self): | |
| return self.func(*[t for t in self.args if isinstance(t, TensExpr)]).doit() | |
| def dum_in_args(self): | |
| arg_offset = self._get_position_offset_for_indices() | |
| argpos = self._get_indices_to_args_pos() | |
| return [(p1-arg_offset[p1], p2-arg_offset[p2], argpos[p1], argpos[p2]) for p1, p2 in self.dum] | |
| def equals(self, other): | |
| if other == 0: | |
| return self.coeff == 0 | |
| other = _sympify(other) | |
| if not isinstance(other, TensExpr): | |
| assert not self.components | |
| return self.coeff == other | |
| return self.canon_bp() == other.canon_bp() | |
| def get_indices(self): | |
| """ | |
| Returns the list of indices of the tensor. | |
| Explanation | |
| =========== | |
| The indices are listed in the order in which they appear in the | |
| component tensors. | |
| The dummy indices are given a name which does not collide with | |
| the names of the free indices. | |
| Examples | |
| ======== | |
| >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, tensor_heads | |
| >>> Lorentz = TensorIndexType('Lorentz', dummy_name='L') | |
| >>> m0, m1, m2 = tensor_indices('m0,m1,m2', Lorentz) | |
| >>> g = Lorentz.metric | |
| >>> p, q = tensor_heads('p,q', [Lorentz]) | |
| >>> t = p(m1)*g(m0,m2) | |
| >>> t.get_indices() | |
| [m1, m0, m2] | |
| >>> t2 = p(m1)*g(-m1, m2) | |
| >>> t2.get_indices() | |
| [L_0, -L_0, m2] | |
| """ | |
| return self._indices | |
| def get_free_indices(self) -> list[TensorIndex]: | |
| """ | |
| Returns the list of free indices of the tensor. | |
| Explanation | |
| =========== | |
| The indices are listed in the order in which they appear in the | |
| component tensors. | |
| Examples | |
| ======== | |
| >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, tensor_heads | |
| >>> Lorentz = TensorIndexType('Lorentz', dummy_name='L') | |
| >>> m0, m1, m2 = tensor_indices('m0,m1,m2', Lorentz) | |
| >>> g = Lorentz.metric | |
| >>> p, q = tensor_heads('p,q', [Lorentz]) | |
| >>> t = p(m1)*g(m0,m2) | |
| >>> t.get_free_indices() | |
| [m1, m0, m2] | |
| >>> t2 = p(m1)*g(-m1, m2) | |
| >>> t2.get_free_indices() | |
| [m2] | |
| """ | |
| return self._index_structure.get_free_indices() | |
| def _replace_indices(self, repl: dict[TensorIndex, TensorIndex]) -> TensExpr: | |
| return self.func(*[arg._replace_indices(repl) if isinstance(arg, TensExpr) else arg for arg in self.args]) | |
| def split(self): | |
| """ | |
| Returns a list of tensors, whose product is ``self``. | |
| Explanation | |
| =========== | |
| Dummy indices contracted among different tensor components | |
| become free indices with the same name as the one used to | |
| represent the dummy indices. | |
| Examples | |
| ======== | |
| >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, tensor_heads, TensorSymmetry | |
| >>> Lorentz = TensorIndexType('Lorentz', dummy_name='L') | |
| >>> a, b, c, d = tensor_indices('a,b,c,d', Lorentz) | |
| >>> A, B = tensor_heads('A,B', [Lorentz]*2, TensorSymmetry.fully_symmetric(2)) | |
| >>> t = A(a,b)*B(-b,c) | |
| >>> t | |
| A(a, L_0)*B(-L_0, c) | |
| >>> t.split() | |
| [A(a, L_0), B(-L_0, c)] | |
| """ | |
| if self.args == (): | |
| return [self] | |
| splitp = [] | |
| res = 1 | |
| for arg in self.args: | |
| if isinstance(arg, Tensor): | |
| splitp.append(res*arg) | |
| res = 1 | |
| else: | |
| res *= arg | |
| return splitp | |
| def _expand(self, **hints): | |
| # TODO: temporary solution, in the future this should be linked to | |
| # `Expr.expand`. | |
| args = [_expand(arg, **hints) for arg in self.args] | |
| args1 = [arg.args if isinstance(arg, (Add, TensAdd)) else (arg,) for arg in args] | |
| return TensAdd(*[ | |
| TensMul(*i) for i in itertools.product(*args1)] | |
| ) | |
| def __neg__(self): | |
| return TensMul(S.NegativeOne, self, is_canon_bp=self._is_canon_bp).doit() | |
| def __getitem__(self, item): | |
| deprecate_data() | |
| with ignore_warnings(SymPyDeprecationWarning): | |
| return self.data[item] | |
| def _get_args_for_traditional_printer(self): | |
| args = list(self.args) | |
| if self.coeff.could_extract_minus_sign(): | |
| # expressions like "-A(a)" | |
| sign = "-" | |
| if args[0] == S.NegativeOne: | |
| args = args[1:] | |
| else: | |
| args[0] = -args[0] | |
| else: | |
| sign = "" | |
| return sign, args | |
| def _sort_args_for_sorted_components(self): | |
| """ | |
| Returns the ``args`` sorted according to the components commutation | |
| properties. | |
| Explanation | |
| =========== | |
| The sorting is done taking into account the commutation group | |
| of the component tensors. | |
| """ | |
| cv = [arg for arg in self.args if isinstance(arg, TensExpr)] | |
| sign = 1 | |
| n = len(cv) - 1 | |
| for i in range(n): | |
| for j in range(n, i, -1): | |
| c = cv[j-1].commutes_with(cv[j]) | |
| # if `c` is `None`, it does neither commute nor anticommute, skip: | |
| if c not in (0, 1): | |
| continue | |
| typ1 = sorted(set(cv[j-1].component.index_types), key=lambda x: x.name) | |
| typ2 = sorted(set(cv[j].component.index_types), key=lambda x: x.name) | |
| if (typ1, cv[j-1].component.name) > (typ2, cv[j].component.name): | |
| cv[j-1], cv[j] = cv[j], cv[j-1] | |
| # if `c` is 1, the anticommute, so change sign: | |
| if c: | |
| sign = -sign | |
| coeff = sign * self.coeff | |
| if coeff != 1: | |
| return [coeff] + cv | |
| return cv | |
| def sorted_components(self): | |
| """ | |
| Returns a tensor product with sorted components. | |
| """ | |
| return TensMul(*self._sort_args_for_sorted_components()).doit() | |
| def perm2tensor(self, g, is_canon_bp=False): | |
| """ | |
| Returns the tensor corresponding to the permutation ``g`` | |
| For further details, see the method in ``TIDS`` with the same name. | |
| """ | |
| return perm2tensor(self, g, is_canon_bp=is_canon_bp) | |
| def canon_bp(self): | |
| """ | |
| Canonicalize using the Butler-Portugal algorithm for canonicalization | |
| under monoterm symmetries. | |
| Examples | |
| ======== | |
| >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, TensorHead, TensorSymmetry | |
| >>> Lorentz = TensorIndexType('Lorentz', dummy_name='L') | |
| >>> m0, m1, m2 = tensor_indices('m0,m1,m2', Lorentz) | |
| >>> A = TensorHead('A', [Lorentz]*2, TensorSymmetry.fully_symmetric(-2)) | |
| >>> t = A(m0,-m1)*A(m1,-m0) | |
| >>> t.canon_bp() | |
| -A(L_0, L_1)*A(-L_0, -L_1) | |
| >>> t = A(m0,-m1)*A(m1,-m2)*A(m2,-m0) | |
| >>> t.canon_bp() | |
| 0 | |
| """ | |
| if self._is_canon_bp: | |
| return self | |
| expr = self.expand() | |
| if isinstance(expr, TensAdd): | |
| return expr.canon_bp() | |
| if not expr.components: | |
| return expr | |
| t = expr.sorted_components() | |
| g, dummies, msym = t._index_structure.indices_canon_args() | |
| v = components_canon_args(t.components) | |
| can = canonicalize(g, dummies, msym, *v) | |
| if can == 0: | |
| return S.Zero | |
| tmul = t.perm2tensor(can, True) | |
| return tmul | |
| def contract_delta(self, delta): | |
| t = self.contract_metric(delta) | |
| return t | |
| def _get_indices_to_args_pos(self): | |
| """ | |
| Get a dict mapping the index position to TensMul's argument number. | |
| """ | |
| pos_map = {} | |
| pos_counter = 0 | |
| for arg_i, arg in enumerate(self.args): | |
| if not isinstance(arg, TensExpr): | |
| continue | |
| assert isinstance(arg, Tensor) | |
| for i in range(arg.ext_rank): | |
| pos_map[pos_counter] = arg_i | |
| pos_counter += 1 | |
| return pos_map | |
| def contract_metric(self, g): | |
| """ | |
| Raise or lower indices with the metric ``g``. | |
| Parameters | |
| ========== | |
| g : metric | |
| Notes | |
| ===== | |
| See the ``TensorIndexType`` docstring for the contraction conventions. | |
| Examples | |
| ======== | |
| >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, tensor_heads | |
| >>> Lorentz = TensorIndexType('Lorentz', dummy_name='L') | |
| >>> m0, m1, m2 = tensor_indices('m0,m1,m2', Lorentz) | |
| >>> g = Lorentz.metric | |
| >>> p, q = tensor_heads('p,q', [Lorentz]) | |
| >>> t = p(m0)*q(m1)*g(-m0, -m1) | |
| >>> t.canon_bp() | |
| metric(L_0, L_1)*p(-L_0)*q(-L_1) | |
| >>> t.contract_metric(g).canon_bp() | |
| p(L_0)*q(-L_0) | |
| """ | |
| expr = self.expand() | |
| if self != expr: | |
| expr = canon_bp(expr) | |
| return contract_metric(expr, g) | |
| pos_map = self._get_indices_to_args_pos() | |
| args = list(self.args) | |
| #antisym = g.index_types[0].metric_antisym | |
| if g.symmetry == TensorSymmetry.fully_symmetric(-2): | |
| antisym = 1 | |
| elif g.symmetry == TensorSymmetry.fully_symmetric(2): | |
| antisym = 0 | |
| elif g.symmetry == TensorSymmetry.no_symmetry(2): | |
| antisym = None | |
| else: | |
| raise NotImplementedError | |
| # list of positions of the metric ``g`` inside ``args`` | |
| gpos = [i for i, x in enumerate(self.args) if isinstance(x, Tensor) and x.component == g] | |
| if not gpos: | |
| return self | |
| # Sign is either 1 or -1, to correct the sign after metric contraction | |
| # (for spinor indices). | |
| sign = 1 | |
| dum = self.dum[:] | |
| free = self.free[:] | |
| elim = set() | |
| for gposx in gpos: | |
| if gposx in elim: | |
| continue | |
| free1 = [x for x in free if pos_map[x[1]] == gposx] | |
| dum1 = [x for x in dum if pos_map[x[0]] == gposx or pos_map[x[1]] == gposx] | |
| if not dum1: | |
| continue | |
| elim.add(gposx) | |
| # subs with the multiplication neutral element, that is, remove it: | |
| args[gposx] = 1 | |
| if len(dum1) == 2: | |
| if not antisym: | |
| dum10, dum11 = dum1 | |
| if pos_map[dum10[1]] == gposx: | |
| # the index with pos p0 contravariant | |
| p0 = dum10[0] | |
| else: | |
| # the index with pos p0 is covariant | |
| p0 = dum10[1] | |
| if pos_map[dum11[1]] == gposx: | |
| # the index with pos p1 is contravariant | |
| p1 = dum11[0] | |
| else: | |
| # the index with pos p1 is covariant | |
| p1 = dum11[1] | |
| dum.append((p0, p1)) | |
| else: | |
| dum10, dum11 = dum1 | |
| # change the sign to bring the indices of the metric to contravariant | |
| # form; change the sign if dum10 has the metric index in position 0 | |
| if pos_map[dum10[1]] == gposx: | |
| # the index with pos p0 is contravariant | |
| p0 = dum10[0] | |
| if dum10[1] == 1: | |
| sign = -sign | |
| else: | |
| # the index with pos p0 is covariant | |
| p0 = dum10[1] | |
| if dum10[0] == 0: | |
| sign = -sign | |
| if pos_map[dum11[1]] == gposx: | |
| # the index with pos p1 is contravariant | |
| p1 = dum11[0] | |
| sign = -sign | |
| else: | |
| # the index with pos p1 is covariant | |
| p1 = dum11[1] | |
| dum.append((p0, p1)) | |
| elif len(dum1) == 1: | |
| if not antisym: | |
| dp0, dp1 = dum1[0] | |
| if pos_map[dp0] == pos_map[dp1]: | |
| # g(i, -i) | |
| typ = g.index_types[0] | |
| sign = sign*typ.dim | |
| else: | |
| # g(i0, i1)*p(-i1) | |
| if pos_map[dp0] == gposx: | |
| p1 = dp1 | |
| else: | |
| p1 = dp0 | |
| ind, p = free1[0] | |
| free.append((ind, p1)) | |
| else: | |
| dp0, dp1 = dum1[0] | |
| if pos_map[dp0] == pos_map[dp1]: | |
| # g(i, -i) | |
| typ = g.index_types[0] | |
| sign = sign*typ.dim | |
| if dp0 < dp1: | |
| # g(i, -i) = -D with antisymmetric metric | |
| sign = -sign | |
| else: | |
| # g(i0, i1)*p(-i1) | |
| if pos_map[dp0] == gposx: | |
| p1 = dp1 | |
| if dp0 == 0: | |
| sign = -sign | |
| else: | |
| p1 = dp0 | |
| ind, p = free1[0] | |
| free.append((ind, p1)) | |
| dum = [x for x in dum if x not in dum1] | |
| free = [x for x in free if x not in free1] | |
| # shift positions: | |
| shift = 0 | |
| shifts = [0]*len(args) | |
| for i in range(len(args)): | |
| if i in elim: | |
| shift += 2 | |
| continue | |
| shifts[i] = shift | |
| free = [(ind, p - shifts[pos_map[p]]) for (ind, p) in free if pos_map[p] not in elim] | |
| dum = [(p0 - shifts[pos_map[p0]], p1 - shifts[pos_map[p1]]) for p0, p1 in dum if pos_map[p0] not in elim and pos_map[p1] not in elim] | |
| res = sign*TensMul(*args).doit() | |
| if not isinstance(res, TensExpr): | |
| return res | |
| im = _IndexStructure.from_components_free_dum(res.components, free, dum) | |
| return res._set_new_index_structure(im) | |
| def _set_new_index_structure(self, im, is_canon_bp=False): | |
| indices = im.get_indices() | |
| return self._set_indices(*indices, is_canon_bp=is_canon_bp) | |
| def _set_indices(self, *indices, is_canon_bp=False, **kw_args): | |
| if len(indices) != self.ext_rank: | |
| raise ValueError("indices length mismatch") | |
| args = list(self.args)[:] | |
| pos = 0 | |
| for i, arg in enumerate(args): | |
| if not isinstance(arg, TensExpr): | |
| continue | |
| assert isinstance(arg, Tensor) | |
| ext_rank = arg.ext_rank | |
| args[i] = arg._set_indices(*indices[pos:pos+ext_rank]) | |
| pos += ext_rank | |
| return TensMul(*args, is_canon_bp=is_canon_bp).doit() | |
| def _index_replacement_for_contract_metric(args, free, dum): | |
| for arg in args: | |
| if not isinstance(arg, TensExpr): | |
| continue | |
| assert isinstance(arg, Tensor) | |
| def substitute_indices(self, *index_tuples): | |
| new_args = [] | |
| for arg in self.args: | |
| if isinstance(arg, TensExpr): | |
| arg = arg.substitute_indices(*index_tuples) | |
| new_args.append(arg) | |
| return TensMul(*new_args).doit() | |
| def __call__(self, *indices): | |
| deprecate_call() | |
| free_args = self.free_args | |
| indices = list(indices) | |
| if [x.tensor_index_type for x in indices] != [x.tensor_index_type for x in free_args]: | |
| raise ValueError('incompatible types') | |
| if indices == free_args: | |
| return self | |
| t = self.substitute_indices(*list(zip(free_args, indices))) | |
| # object is rebuilt in order to make sure that all contracted indices | |
| # get recognized as dummies, but only if there are contracted indices. | |
| if len({i if i.is_up else -i for i in indices}) != len(indices): | |
| return t.func(*t.args) | |
| return t | |
| def _extract_data(self, replacement_dict): | |
| args_indices, arrays = zip(*[arg._extract_data(replacement_dict) for arg in self.args if isinstance(arg, TensExpr)]) | |
| coeff = reduce(operator.mul, [a for a in self.args if not isinstance(a, TensExpr)], S.One) | |
| indices, free, free_names, dummy_data = TensMul._indices_to_free_dum(args_indices) | |
| dum = TensMul._dummy_data_to_dum(dummy_data) | |
| ext_rank = self.ext_rank | |
| free.sort(key=lambda x: x[1]) | |
| free_indices = [i[0] for i in free] | |
| return free_indices, coeff*_TensorDataLazyEvaluator.data_contract_dum(arrays, dum, ext_rank) | |
| def data(self): | |
| deprecate_data() | |
| with ignore_warnings(SymPyDeprecationWarning): | |
| dat = _tensor_data_substitution_dict[self.expand()] | |
| return dat | |
| def data(self, data): | |
| deprecate_data() | |
| raise ValueError("Not possible to set component data to a tensor expression") | |
| def data(self): | |
| deprecate_data() | |
| raise ValueError("Not possible to delete component data to a tensor expression") | |
| def __iter__(self): | |
| deprecate_data() | |
| with ignore_warnings(SymPyDeprecationWarning): | |
| if self.data is None: | |
| raise ValueError("No iteration on abstract tensors") | |
| return self.data.__iter__() | |
| def _dedupe_indices(new, exclude): | |
| """ | |
| exclude: set | |
| new: TensExpr | |
| If ``new`` has any dummy indices that are in ``exclude``, return a version | |
| of new with those indices replaced. If no replacements are needed, | |
| return None | |
| """ | |
| exclude = set(exclude) | |
| dums_new = set(get_dummy_indices(new)) | |
| free_new = set(get_free_indices(new)) | |
| conflicts = dums_new.intersection(exclude) | |
| if len(conflicts) == 0: | |
| return None | |
| """ | |
| ``exclude_for_gen`` is to be passed to ``_IndexStructure._get_generator_for_dummy_indices()``. | |
| Since the latter does not use the index position for anything, we just | |
| set it as ``None`` here. | |
| """ | |
| exclude.update(dums_new) | |
| exclude.update(free_new) | |
| exclude_for_gen = [(i, None) for i in exclude] | |
| gen = _IndexStructure._get_generator_for_dummy_indices(exclude_for_gen) | |
| repl = {} | |
| for d in conflicts: | |
| if -d in repl.keys(): | |
| continue | |
| newname = gen(d.tensor_index_type) | |
| new_d = d.func(newname, *d.args[1:]) | |
| repl[d] = new_d | |
| repl[-d] = -new_d | |
| if len(repl) == 0: | |
| return None | |
| new_renamed = new._replace_indices(repl) | |
| return new_renamed | |
| def _dedupe_indices_in_rule(self, rule): | |
| """ | |
| rule: dict | |
| This applies TensMul._dedupe_indices on all values of rule. | |
| """ | |
| index_rules = {k:v for k,v in rule.items() if isinstance(k, TensorIndex)} | |
| other_rules = {k:v for k,v in rule.items() if k not in index_rules.keys()} | |
| exclude = set(self.get_indices()) | |
| newrule = {} | |
| newrule.update(index_rules) | |
| exclude.update(index_rules.keys()) | |
| exclude.update(index_rules.values()) | |
| for old, new in other_rules.items(): | |
| new_renamed = TensMul._dedupe_indices(new, exclude) | |
| if old == new or new_renamed is None: | |
| newrule[old] = new | |
| else: | |
| newrule[old] = new_renamed | |
| exclude.update(get_indices(new_renamed)) | |
| return newrule | |
| def _eval_rewrite_as_Indexed(self, *args, **kwargs): | |
| from sympy.concrete.summations import Sum | |
| index_symbols = [i.args[0] for i in self.get_indices()] | |
| args = [arg.args[0] if isinstance(arg, Sum) else arg for arg in args] | |
| expr = Mul.fromiter(args) | |
| return self._check_add_Sum(expr, index_symbols) | |
| def _eval_partial_derivative(self, s): | |
| # Evaluation like Mul | |
| terms = [] | |
| for i, arg in enumerate(self.args): | |
| # checking whether some tensor instance is differentiated | |
| # or some other thing is necessary, but ugly | |
| if isinstance(arg, TensExpr): | |
| d = arg._eval_partial_derivative(s) | |
| else: | |
| # do not call diff is s is no symbol | |
| if s._diff_wrt: | |
| d = arg._eval_derivative(s) | |
| else: | |
| d = S.Zero | |
| if d: | |
| terms.append(TensMul.fromiter(self.args[:i] + (d,) + self.args[i + 1:])) | |
| return TensAdd.fromiter(terms) | |
| class TensorElement(TensExpr): | |
| """ | |
| Tensor with evaluated components. | |
| Examples | |
| ======== | |
| >>> from sympy.tensor.tensor import TensorIndexType, TensorHead, TensorSymmetry | |
| >>> from sympy import symbols | |
| >>> L = TensorIndexType("L") | |
| >>> i, j, k = symbols("i j k") | |
| >>> A = TensorHead("A", [L, L], TensorSymmetry.fully_symmetric(2)) | |
| >>> A(i, j).get_free_indices() | |
| [i, j] | |
| If we want to set component ``i`` to a specific value, use the | |
| ``TensorElement`` class: | |
| >>> from sympy.tensor.tensor import TensorElement | |
| >>> te = TensorElement(A(i, j), {i: 2}) | |
| As index ``i`` has been accessed (``{i: 2}`` is the evaluation of its 3rd | |
| element), the free indices will only contain ``j``: | |
| >>> te.get_free_indices() | |
| [j] | |
| """ | |
| def __new__(cls, expr, index_map): | |
| if not isinstance(expr, Tensor): | |
| # remap | |
| if not isinstance(expr, TensExpr): | |
| raise TypeError("%s is not a tensor expression" % expr) | |
| return expr.func(*[TensorElement(arg, index_map) for arg in expr.args]) | |
| expr_free_indices = expr.get_free_indices() | |
| name_translation = {i.args[0]: i for i in expr_free_indices} | |
| index_map = {name_translation.get(index, index): value for index, value in index_map.items()} | |
| index_map = {index: value for index, value in index_map.items() if index in expr_free_indices} | |
| if len(index_map) == 0: | |
| return expr | |
| free_indices = [i for i in expr_free_indices if i not in index_map.keys()] | |
| index_map = Dict(index_map) | |
| obj = TensExpr.__new__(cls, expr, index_map) | |
| obj._free_indices = free_indices | |
| return obj | |
| def free(self): | |
| return [(index, i) for i, index in enumerate(self.get_free_indices())] | |
| def dum(self): | |
| # TODO: inherit dummies from expr | |
| return [] | |
| def expr(self): | |
| return self._args[0] | |
| def index_map(self): | |
| return self._args[1] | |
| def coeff(self): | |
| return S.One | |
| def nocoeff(self): | |
| return self | |
| def get_free_indices(self): | |
| return self._free_indices | |
| def _replace_indices(self, repl: dict[TensorIndex, TensorIndex]) -> TensExpr: | |
| # TODO: can be improved: | |
| return self.xreplace(repl) | |
| def get_indices(self): | |
| return self.get_free_indices() | |
| def _extract_data(self, replacement_dict): | |
| ret_indices, array = self.expr._extract_data(replacement_dict) | |
| index_map = self.index_map | |
| slice_tuple = tuple(index_map.get(i, slice(None)) for i in ret_indices) | |
| ret_indices = [i for i in ret_indices if i not in index_map] | |
| array = array.__getitem__(slice_tuple) | |
| return ret_indices, array | |
| class WildTensorHead(TensorHead): | |
| """ | |
| A wild object that is used to create ``WildTensor`` instances | |
| Explanation | |
| =========== | |
| Examples | |
| ======== | |
| >>> from sympy.tensor.tensor import TensorHead, TensorIndex, WildTensorHead, TensorIndexType | |
| >>> R3 = TensorIndexType('R3', dim=3) | |
| >>> p = TensorIndex('p', R3) | |
| >>> q = TensorIndex('q', R3) | |
| A WildTensorHead can be created without specifying a ``TensorIndexType`` | |
| >>> W = WildTensorHead("W") | |
| Calling it with a ``TensorIndex`` creates a ``WildTensor`` instance. | |
| >>> type(W(p)) | |
| <class 'sympy.tensor.tensor.WildTensor'> | |
| The ``TensorIndexType`` is automatically detected from the index that is passed | |
| >>> W(p).component | |
| W(R3) | |
| Calling it with no indices returns an object that can match tensors with any number of indices. | |
| >>> K = TensorHead('K', [R3]) | |
| >>> Q = TensorHead('Q', [R3, R3]) | |
| >>> W().matches(K(p)) | |
| {W: K(p)} | |
| >>> W().matches(Q(p,q)) | |
| {W: Q(p, q)} | |
| If you want to ignore the order of indices while matching, pass ``unordered_indices=True``. | |
| >>> U = WildTensorHead("U", unordered_indices=True) | |
| >>> W(p,q).matches(Q(q,p)) | |
| >>> U(p,q).matches(Q(q,p)) | |
| {U(R3,R3): _WildTensExpr(Q(q, p))} | |
| Parameters | |
| ========== | |
| name : name of the tensor | |
| unordered_indices : whether the order of the indices matters for matching | |
| (default: False) | |
| See also | |
| ======== | |
| ``WildTensor`` | |
| ``TensorHead`` | |
| """ | |
| def __new__(cls, name, index_types=None, symmetry=None, comm=0, unordered_indices=False): | |
| if isinstance(name, str): | |
| name_symbol = Symbol(name) | |
| elif isinstance(name, Symbol): | |
| name_symbol = name | |
| else: | |
| raise ValueError("invalid name") | |
| if index_types is None: | |
| index_types = [] | |
| if symmetry is None: | |
| symmetry = TensorSymmetry.no_symmetry(len(index_types)) | |
| else: | |
| assert symmetry.rank == len(index_types) | |
| if symmetry != TensorSymmetry.no_symmetry(len(index_types)): | |
| raise NotImplementedError("Wild matching based on symmetry is not implemented.") | |
| obj = Basic.__new__(cls, name_symbol, Tuple(*index_types), sympify(symmetry), sympify(comm), sympify(unordered_indices)) | |
| return obj | |
| def unordered_indices(self): | |
| return self.args[4] | |
| def __call__(self, *indices, **kwargs): | |
| tensor = WildTensor(self, indices, **kwargs) | |
| return tensor.doit() | |
| class WildTensor(Tensor): | |
| """ | |
| A wild object which matches ``Tensor`` instances | |
| Explanation | |
| =========== | |
| This is instantiated by attaching indices to a ``WildTensorHead`` instance. | |
| Examples | |
| ======== | |
| >>> from sympy.tensor.tensor import TensorHead, TensorIndex, WildTensorHead, TensorIndexType | |
| >>> W = WildTensorHead("W") | |
| >>> R3 = TensorIndexType('R3', dim=3) | |
| >>> p = TensorIndex('p', R3) | |
| >>> q = TensorIndex('q', R3) | |
| >>> K = TensorHead('K', [R3]) | |
| >>> Q = TensorHead('Q', [R3, R3]) | |
| Matching also takes the indices into account | |
| >>> W(p).matches(K(p)) | |
| {W(R3): _WildTensExpr(K(p))} | |
| >>> W(p).matches(K(q)) | |
| >>> W(p).matches(K(-p)) | |
| If you want to match objects with any number of indices, just use a ``WildTensor`` with no indices. | |
| >>> W().matches(K(p)) | |
| {W: K(p)} | |
| >>> W().matches(Q(p,q)) | |
| {W: Q(p, q)} | |
| See Also | |
| ======== | |
| ``WildTensorHead`` | |
| ``Tensor`` | |
| """ | |
| def __new__(cls, tensor_head, indices, **kw_args): | |
| is_canon_bp = kw_args.pop("is_canon_bp", False) | |
| if tensor_head.func == TensorHead: | |
| """ | |
| If someone tried to call WildTensor by supplying a TensorHead (not a WildTensorHead), return a normal tensor instead. This is helpful when using subs on an expression to replace occurrences of a WildTensorHead with a TensorHead. | |
| """ | |
| return Tensor(tensor_head, indices, is_canon_bp=is_canon_bp, **kw_args) | |
| elif tensor_head.func == _WildTensExpr: | |
| return tensor_head(*indices) | |
| indices = cls._parse_indices(tensor_head, indices) | |
| index_types = [ind.tensor_index_type for ind in indices] | |
| tensor_head = tensor_head.func( | |
| tensor_head.name, | |
| index_types, | |
| symmetry=None, | |
| comm=tensor_head.comm, | |
| unordered_indices=tensor_head.unordered_indices, | |
| ) | |
| obj = Basic.__new__(cls, tensor_head, Tuple(*indices)) | |
| obj.name = tensor_head.name | |
| obj._index_structure = _IndexStructure.from_indices(*indices) | |
| obj._free = obj._index_structure.free[:] | |
| obj._dum = obj._index_structure.dum[:] | |
| obj._ext_rank = obj._index_structure._ext_rank | |
| obj._coeff = S.One | |
| obj._nocoeff = obj | |
| obj._component = tensor_head | |
| obj._components = [tensor_head] | |
| if tensor_head.rank != len(indices): | |
| raise ValueError("wrong number of indices") | |
| obj.is_canon_bp = is_canon_bp | |
| obj._index_map = obj._build_index_map(indices, obj._index_structure) | |
| return obj | |
| def matches(self, expr, repl_dict=None, old=False): | |
| if not isinstance(expr, TensExpr) and expr != S(1): | |
| return None | |
| if repl_dict is None: | |
| repl_dict = {} | |
| else: | |
| repl_dict = repl_dict.copy() | |
| if len(self.indices) > 0: | |
| if not hasattr(expr, "get_free_indices"): | |
| return None | |
| expr_indices = expr.get_free_indices() | |
| if len(expr_indices) != len(self.indices): | |
| return None | |
| if self._component.unordered_indices: | |
| m = self._match_indices_ignoring_order(expr) | |
| if m is None: | |
| return None | |
| else: | |
| repl_dict.update(m) | |
| else: | |
| for i in range(len(expr_indices)): | |
| m = self.indices[i].matches(expr_indices[i]) | |
| if m is None: | |
| return None | |
| else: | |
| repl_dict.update(m) | |
| repl_dict[self.component] = _WildTensExpr(expr) | |
| else: | |
| #If no indices were passed to the WildTensor, it may match tensors with any number of indices. | |
| repl_dict[self] = expr | |
| return repl_dict | |
| def _match_indices_ignoring_order(self, expr, repl_dict=None, old=False): | |
| """ | |
| Helper method for matches. Checks if the indices of self and expr | |
| match disregarding index ordering. | |
| """ | |
| if repl_dict is None: | |
| repl_dict = {} | |
| else: | |
| repl_dict = repl_dict.copy() | |
| def siftkey(ind): | |
| if isinstance(ind, WildTensorIndex): | |
| if ind.ignore_updown: | |
| return "wild, updown" | |
| else: | |
| return "wild" | |
| else: | |
| return "nonwild" | |
| indices_sifted = sift(self.indices, siftkey) | |
| matched_indices = [] | |
| expr_indices_remaining = expr.get_indices() | |
| for ind in indices_sifted["nonwild"]: | |
| matched_this_ind = False | |
| for e_ind in expr_indices_remaining: | |
| if e_ind in matched_indices: | |
| continue | |
| m = ind.matches(e_ind) | |
| if m is not None: | |
| matched_this_ind = True | |
| repl_dict.update(m) | |
| matched_indices.append(e_ind) | |
| break | |
| if not matched_this_ind: | |
| return None | |
| expr_indices_remaining = [i for i in expr_indices_remaining if i not in matched_indices] | |
| for ind in indices_sifted["wild"]: | |
| matched_this_ind = False | |
| for e_ind in expr_indices_remaining: | |
| m = ind.matches(e_ind) | |
| if m is not None: | |
| if -ind in repl_dict.keys() and -repl_dict[-ind] != m[ind]: | |
| return None | |
| matched_this_ind = True | |
| repl_dict.update(m) | |
| matched_indices.append(e_ind) | |
| break | |
| if not matched_this_ind: | |
| return None | |
| expr_indices_remaining = [i for i in expr_indices_remaining if i not in matched_indices] | |
| for ind in indices_sifted["wild, updown"]: | |
| matched_this_ind = False | |
| for e_ind in expr_indices_remaining: | |
| m = ind.matches(e_ind) | |
| if m is not None: | |
| if -ind in repl_dict.keys() and -repl_dict[-ind] != m[ind]: | |
| return None | |
| matched_this_ind = True | |
| repl_dict.update(m) | |
| matched_indices.append(e_ind) | |
| break | |
| if not matched_this_ind: | |
| return None | |
| if len(matched_indices) < len(self.indices): | |
| return None | |
| else: | |
| return repl_dict | |
| class WildTensorIndex(TensorIndex): | |
| """ | |
| A wild object that matches TensorIndex instances. | |
| Examples | |
| ======== | |
| >>> from sympy.tensor.tensor import TensorIndex, TensorIndexType, WildTensorIndex | |
| >>> R3 = TensorIndexType('R3', dim=3) | |
| >>> p = TensorIndex("p", R3) | |
| By default, covariant indices only match with covariant indices (and | |
| similarly for contravariant) | |
| >>> q = WildTensorIndex("q", R3) | |
| >>> (q).matches(p) | |
| {q: p} | |
| >>> (q).matches(-p) | |
| If you want matching to ignore whether the index is co/contra-variant, set | |
| ignore_updown=True | |
| >>> r = WildTensorIndex("r", R3, ignore_updown=True) | |
| >>> (r).matches(-p) | |
| {r: -p} | |
| >>> (r).matches(p) | |
| {r: p} | |
| Parameters | |
| ========== | |
| name : name of the index (string), or ``True`` if you want it to be | |
| automatically assigned | |
| tensor_index_type : ``TensorIndexType`` of the index | |
| is_up : flag for contravariant index (is_up=True by default) | |
| ignore_updown : bool, Whether this should match both co- and contra-variant | |
| indices (default:False) | |
| """ | |
| def __new__(cls, name, tensor_index_type, is_up=True, ignore_updown=False): | |
| if isinstance(name, str): | |
| name_symbol = Symbol(name) | |
| elif isinstance(name, Symbol): | |
| name_symbol = name | |
| elif name is True: | |
| name = "_i{}".format(len(tensor_index_type._autogenerated)) | |
| name_symbol = Symbol(name) | |
| tensor_index_type._autogenerated.append(name_symbol) | |
| else: | |
| raise ValueError("invalid name") | |
| is_up = sympify(is_up) | |
| ignore_updown = sympify(ignore_updown) | |
| return Basic.__new__(cls, name_symbol, tensor_index_type, is_up, ignore_updown) | |
| def ignore_updown(self): | |
| return self.args[3] | |
| def __neg__(self): | |
| t1 = WildTensorIndex(self.name, self.tensor_index_type, | |
| (not self.is_up), self.ignore_updown) | |
| return t1 | |
| def matches(self, expr, repl_dict=None, old=False): | |
| if not isinstance(expr, TensorIndex): | |
| return None | |
| if self.tensor_index_type != expr.tensor_index_type: | |
| return None | |
| if not self.ignore_updown: | |
| if self.is_up != expr.is_up: | |
| return None | |
| if repl_dict is None: | |
| repl_dict = {} | |
| else: | |
| repl_dict = repl_dict.copy() | |
| repl_dict[self] = expr | |
| return repl_dict | |
| class _WildTensExpr(Basic): | |
| """ | |
| INTERNAL USE ONLY | |
| This is an object that helps with replacement of WildTensors in expressions. | |
| When this object is set as the tensor_head of a WildTensor, it replaces the | |
| WildTensor by a TensExpr (passed when initializing this object). | |
| Examples | |
| ======== | |
| >>> from sympy.tensor.tensor import WildTensorHead, TensorIndex, TensorHead, TensorIndexType | |
| >>> W = WildTensorHead("W") | |
| >>> R3 = TensorIndexType('R3', dim=3) | |
| >>> p = TensorIndex('p', R3) | |
| >>> q = TensorIndex('q', R3) | |
| >>> K = TensorHead('K', [R3]) | |
| >>> print( ( K(p) ).replace( W(p), W(q)*W(-q)*W(p) ) ) | |
| K(R_0)*K(-R_0)*K(p) | |
| """ | |
| def __init__(self, expr): | |
| if not isinstance(expr, TensExpr): | |
| raise TypeError("_WildTensExpr expects a TensExpr as argument") | |
| self.expr = expr | |
| def __call__(self, *indices): | |
| return self.expr._replace_indices(dict(zip(self.expr.get_free_indices(), indices))) | |
| def __neg__(self): | |
| return self.func(self.expr*S.NegativeOne) | |
| def __abs__(self): | |
| raise NotImplementedError | |
| def __add__(self, other): | |
| if other.func != self.func: | |
| raise TypeError(f"Cannot add {self.func} to {other.func}") | |
| return self.func(self.expr+other.expr) | |
| def __radd__(self, other): | |
| if other.func != self.func: | |
| raise TypeError(f"Cannot add {self.func} to {other.func}") | |
| return self.func(other.expr+self.expr) | |
| def __sub__(self, other): | |
| return self + (-other) | |
| def __rsub__(self, other): | |
| return other + (-self) | |
| def __mul__(self, other): | |
| raise NotImplementedError | |
| def __rmul__(self, other): | |
| raise NotImplementedError | |
| def __truediv__(self, other): | |
| raise NotImplementedError | |
| def __rtruediv__(self, other): | |
| raise NotImplementedError | |
| def __pow__(self, other): | |
| raise NotImplementedError | |
| def __rpow__(self, other): | |
| raise NotImplementedError | |
| def canon_bp(p): | |
| """ | |
| Butler-Portugal canonicalization. See ``tensor_can.py`` from the | |
| combinatorics module for the details. | |
| """ | |
| if isinstance(p, TensExpr): | |
| return p.canon_bp() | |
| return p | |
| def tensor_mul(*a): | |
| """ | |
| product of tensors | |
| """ | |
| if not a: | |
| return TensMul.from_data(S.One, [], [], []) | |
| t = a[0] | |
| for tx in a[1:]: | |
| t = t*tx | |
| return t | |
| def riemann_cyclic_replace(t_r): | |
| """ | |
| replace Riemann tensor with an equivalent expression | |
| ``R(m,n,p,q) -> 2/3*R(m,n,p,q) - 1/3*R(m,q,n,p) + 1/3*R(m,p,n,q)`` | |
| """ | |
| free = sorted(t_r.free, key=lambda x: x[1]) | |
| m, n, p, q = [x[0] for x in free] | |
| t0 = t_r*Rational(2, 3) | |
| t1 = -t_r.substitute_indices((m,m),(n,q),(p,n),(q,p))*Rational(1, 3) | |
| t2 = t_r.substitute_indices((m,m),(n,p),(p,n),(q,q))*Rational(1, 3) | |
| t3 = t0 + t1 + t2 | |
| return t3 | |
| def riemann_cyclic(t2): | |
| """ | |
| Replace each Riemann tensor with an equivalent expression | |
| satisfying the cyclic identity. | |
| This trick is discussed in the reference guide to Cadabra. | |
| Examples | |
| ======== | |
| >>> from sympy.tensor.tensor import TensorIndexType, tensor_indices, TensorHead, riemann_cyclic, TensorSymmetry | |
| >>> Lorentz = TensorIndexType('Lorentz', dummy_name='L') | |
| >>> i, j, k, l = tensor_indices('i,j,k,l', Lorentz) | |
| >>> R = TensorHead('R', [Lorentz]*4, TensorSymmetry.riemann()) | |
| >>> t = R(i,j,k,l)*(R(-i,-j,-k,-l) - 2*R(-i,-k,-j,-l)) | |
| >>> riemann_cyclic(t) | |
| 0 | |
| """ | |
| t2 = t2.expand() | |
| if isinstance(t2, (TensMul, Tensor)): | |
| args = [t2] | |
| else: | |
| args = t2.args | |
| a1 = [x.split() for x in args] | |
| a2 = [[riemann_cyclic_replace(tx) for tx in y] for y in a1] | |
| a3 = [tensor_mul(*v) for v in a2] | |
| t3 = TensAdd(*a3).doit() | |
| if not t3: | |
| return t3 | |
| else: | |
| return canon_bp(t3) | |
| def get_lines(ex, index_type): | |
| """ | |
| Returns ``(lines, traces, rest)`` for an index type, | |
| where ``lines`` is the list of list of positions of a matrix line, | |
| ``traces`` is the list of list of traced matrix lines, | |
| ``rest`` is the rest of the elements of the tensor. | |
| """ | |
| def _join_lines(a): | |
| i = 0 | |
| while i < len(a): | |
| x = a[i] | |
| xend = x[-1] | |
| xstart = x[0] | |
| hit = True | |
| while hit: | |
| hit = False | |
| for j in range(i + 1, len(a)): | |
| if j >= len(a): | |
| break | |
| if a[j][0] == xend: | |
| hit = True | |
| x.extend(a[j][1:]) | |
| xend = x[-1] | |
| a.pop(j) | |
| continue | |
| if a[j][0] == xstart: | |
| hit = True | |
| a[i] = reversed(a[j][1:]) + x | |
| x = a[i] | |
| xstart = a[i][0] | |
| a.pop(j) | |
| continue | |
| if a[j][-1] == xend: | |
| hit = True | |
| x.extend(reversed(a[j][:-1])) | |
| xend = x[-1] | |
| a.pop(j) | |
| continue | |
| if a[j][-1] == xstart: | |
| hit = True | |
| a[i] = a[j][:-1] + x | |
| x = a[i] | |
| xstart = x[0] | |
| a.pop(j) | |
| continue | |
| i += 1 | |
| return a | |
| arguments = ex.args | |
| dt = {} | |
| for c in ex.args: | |
| if not isinstance(c, TensExpr): | |
| continue | |
| if c in dt: | |
| continue | |
| index_types = c.index_types | |
| a = [] | |
| for i in range(len(index_types)): | |
| if index_types[i] is index_type: | |
| a.append(i) | |
| if len(a) > 2: | |
| raise ValueError('at most two indices of type %s allowed' % index_type) | |
| if len(a) == 2: | |
| dt[c] = a | |
| #dum = ex.dum | |
| lines = [] | |
| traces = [] | |
| traces1 = [] | |
| #indices_to_args_pos = ex._get_indices_to_args_pos() | |
| # TODO: add a dum_to_components_map ? | |
| for p0, p1, c0, c1 in ex.dum_in_args: | |
| if arguments[c0] not in dt: | |
| continue | |
| if c0 == c1: | |
| traces.append([c0]) | |
| continue | |
| ta0 = dt[arguments[c0]] | |
| ta1 = dt[arguments[c1]] | |
| if p0 not in ta0: | |
| continue | |
| if ta0.index(p0) == ta1.index(p1): | |
| # case gamma(i,s0,-s1) in c0, gamma(j,-s0,s2) in c1; | |
| # to deal with this case one could add to the position | |
| # a flag for transposition; | |
| # one could write [(c0, False), (c1, True)] | |
| raise NotImplementedError | |
| # if p0 == ta0[1] then G in pos c0 is mult on the right by G in c1 | |
| # if p0 == ta0[0] then G in pos c1 is mult on the right by G in c0 | |
| ta0 = dt[arguments[c0]] | |
| b0, b1 = (c0, c1) if p0 == ta0[1] else (c1, c0) | |
| lines1 = lines[:] | |
| for line in lines: | |
| if line[-1] == b0: | |
| if line[0] == b1: | |
| n = line.index(min(line)) | |
| traces1.append(line) | |
| traces.append(line[n:] + line[:n]) | |
| else: | |
| line.append(b1) | |
| break | |
| elif line[0] == b1: | |
| line.insert(0, b0) | |
| break | |
| else: | |
| lines1.append([b0, b1]) | |
| lines = [x for x in lines1 if x not in traces1] | |
| lines = _join_lines(lines) | |
| rest = [] | |
| for line in lines: | |
| for y in line: | |
| rest.append(y) | |
| for line in traces: | |
| for y in line: | |
| rest.append(y) | |
| rest = [x for x in range(len(arguments)) if x not in rest] | |
| return lines, traces, rest | |
| def get_free_indices(t): | |
| if not isinstance(t, TensExpr): | |
| return () | |
| return t.get_free_indices() | |
| def get_indices(t): | |
| if not isinstance(t, TensExpr): | |
| return () | |
| return t.get_indices() | |
| def get_dummy_indices(t): | |
| if not isinstance(t, TensExpr): | |
| return () | |
| inds = t.get_indices() | |
| free = t.get_free_indices() | |
| return [i for i in inds if i not in free] | |
| def get_index_structure(t): | |
| if isinstance(t, TensExpr): | |
| return t._index_structure | |
| return _IndexStructure([], [], [], []) | |
| def get_coeff(t): | |
| if isinstance(t, Tensor): | |
| return S.One | |
| if isinstance(t, TensMul): | |
| return t.coeff | |
| if isinstance(t, TensExpr): | |
| raise ValueError("no coefficient associated to this tensor expression") | |
| return t | |
| def contract_metric(t, g): | |
| if isinstance(t, TensExpr): | |
| return t.contract_metric(g) | |
| return t | |
| def perm2tensor(t, g, is_canon_bp=False): | |
| """ | |
| Returns the tensor corresponding to the permutation ``g`` | |
| For further details, see the method in ``TIDS`` with the same name. | |
| """ | |
| if not isinstance(t, TensExpr): | |
| return t | |
| elif isinstance(t, (Tensor, TensMul)): | |
| nim = get_index_structure(t).perm2tensor(g, is_canon_bp=is_canon_bp) | |
| res = t._set_new_index_structure(nim, is_canon_bp=is_canon_bp) | |
| if g[-1] != len(g) - 1: | |
| return -res | |
| return res | |
| raise NotImplementedError() | |
| def substitute_indices(t, *index_tuples): | |
| if not isinstance(t, TensExpr): | |
| return t | |
| return t.substitute_indices(*index_tuples) | |
| def _expand(expr, **kwargs): | |
| if isinstance(expr, TensExpr): | |
| return expr._expand(**kwargs) | |
| else: | |
| return expr.expand(**kwargs) | |
| def get_postprocessor(cls): | |
| def _postprocessor(expr): | |
| tens_class = {Mul: TensMul, Add: TensAdd}[cls] | |
| if any(isinstance(a, TensExpr) for a in expr.args): | |
| return tens_class(*expr.args) | |
| else: | |
| return expr | |
| return _postprocessor | |
| Basic._constructor_postprocessor_mapping[TensExpr] = { | |
| "Mul": [get_postprocessor(Mul)], | |
| } | |