From 3a65d2d2c7f89d3d74109444b1501b3709bd6fce Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 29 Aug 2022 09:56:39 -0700 Subject: [PATCH 01/17] CompWithSym._canonicalize_sym_antisym: Factor out from __init__ --- src/sage/tensor/modules/comp.py | 37 +++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/src/sage/tensor/modules/comp.py b/src/sage/tensor/modules/comp.py index 04f14f782f0..8de1f365212 100644 --- a/src/sage/tensor/modules/comp.py +++ b/src/sage/tensor/modules/comp.py @@ -2996,7 +2996,21 @@ def __init__(self, ring, frame, nb_indices, start_index=0, """ Components.__init__(self, ring, frame, nb_indices, start_index, output_formatter) - self._sym = [] + self._sym, self._antisym = self._canonicalize_sym_antisym( + nb_indices, sym, antisym) + + @staticmethod + def _canonicalize_sym_antisym(nb_indices, sym=None, antisym=None): + r""" + Bring sym and antisym into their canonical form. + + EXAMPLES:: + + sage: from sage.tensor.modules.comp import CompWithSym + sage: CompWithSym._canonicalize_sym_antisym(6, [(2, 1)]) + ([(2, 1)], []) + """ + result_sym = [] if sym is not None and sym != []: if isinstance(sym[0], (int, Integer)): # a single symmetry is provided as a tuple or a range object; @@ -3007,11 +3021,11 @@ def __init__(self, ring, frame, nb_indices, start_index=0, raise IndexError("at least two index positions must be " + "provided to define a symmetry") for i in isym: - if i<0 or i>self._nid-1: + if i < 0 or i > nb_indices - 1: raise IndexError("invalid index position: " + str(i) + - " not in [0," + str(self._nid-1) + "]") - self._sym.append(tuple(isym)) - self._antisym = [] + " not in [0," + str(nb_indices-1) + "]") + result_sym.append(tuple(isym)) + result_antisym = [] if antisym is not None and antisym != []: if isinstance(antisym[0], (int, Integer)): # a single antisymmetry is provided as a tuple or a range @@ -3022,20 +3036,21 @@ def __init__(self, ring, frame, nb_indices, start_index=0, raise IndexError("at least two index positions must be " + "provided to define an antisymmetry") for i in isym: - if i<0 or i>self._nid-1: + if i < 0 or i > nb_indices - 1: raise IndexError("invalid index position: " + str(i) + - " not in [0," + str(self._nid-1) + "]") - self._antisym.append(tuple(isym)) + " not in [0," + str(nb_indices - 1) + "]") + result_antisym.append(tuple(isym)) # Final consistency check: index_list = [] - for isym in self._sym: + for isym in result_sym: index_list += isym - for isym in self._antisym: + for isym in result_antisym: index_list += isym if len(index_list) != len(set(index_list)): # There is a repeated index position: raise IndexError("incompatible lists of symmetries: the same " + - "index position appears more then once") + "index position appears more than once") + return result_sym, result_antisym def _repr_(self): r""" From 07ab9914adf5418070b9f326d99d282c18ddb35a Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 29 Aug 2022 10:52:35 -0700 Subject: [PATCH 02/17] src/sage/tensor: Allow _sym, _antisym attributes to be tuples --- .../manifolds/differentiable/tensorfield.py | 4 +-- src/sage/tensor/modules/comp.py | 26 +++++++++++++------ src/sage/tensor/modules/free_module_tensor.py | 4 +-- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/sage/manifolds/differentiable/tensorfield.py b/src/sage/manifolds/differentiable/tensorfield.py index d5b63c9d81d..1370b104140 100644 --- a/src/sage/manifolds/differentiable/tensorfield.py +++ b/src/sage/manifolds/differentiable/tensorfield.py @@ -957,13 +957,13 @@ def symmetries(self): elif len(self._sym) == 1: s = "symmetry: {}; ".format(self._sym[0]) else: - s = "symmetries: {}; ".format(self._sym) + s = "symmetries: {}; ".format(list(self._sym)) if not self._antisym: a = "no antisymmetry" elif len(self._antisym) == 1: a = "antisymmetry: {}".format(self._antisym[0]) else: - a = "antisymmetries: {}".format(self._antisym) + a = "antisymmetries: {}".format(list(self._antisym)) print(s + a) #### End of simple accessors ##### diff --git a/src/sage/tensor/modules/comp.py b/src/sage/tensor/modules/comp.py index 8de1f365212..d9c3dfdfe3e 100644 --- a/src/sage/tensor/modules/comp.py +++ b/src/sage/tensor/modules/comp.py @@ -1773,12 +1773,12 @@ def __mul__(self, other): "same starting index") if isinstance(other, CompWithSym): sym = [] - if other._sym != []: + if other._sym: for s in other._sym: ns = tuple(s[i]+self._nid for i in range(len(s))) sym.append(ns) antisym = [] - if other._antisym != []: + if other._antisym: for s in other._antisym: ns = tuple(s[i]+self._nid for i in range(len(s))) antisym.append(ns) @@ -3011,7 +3011,12 @@ def _canonicalize_sym_antisym(nb_indices, sym=None, antisym=None): ([(2, 1)], []) """ result_sym = [] - if sym is not None and sym != []: + if sym is None: + sym = [] + else: + # Handle the case that sym is an iterator + sym = list(sym) + if sym: if isinstance(sym[0], (int, Integer)): # a single symmetry is provided as a tuple or a range object; # it is converted to a 1-item list: @@ -3026,7 +3031,12 @@ def _canonicalize_sym_antisym(nb_indices, sym=None, antisym=None): " not in [0," + str(nb_indices-1) + "]") result_sym.append(tuple(isym)) result_antisym = [] - if antisym is not None and antisym != []: + if antisym is None: + antisym = [] + else: + # Handle the case that antisym is an iterator + antisym = list(antisym) + if antisym: if isinstance(antisym[0], (int, Integer)): # a single antisymmetry is provided as a tuple or a range # object; it is converted to a 1-item list: @@ -3535,7 +3545,7 @@ def paral_sum(a, b, local_list_ind): com = tuple(set(isym).intersection(set(osym))) if len(com) > 1: common_antisym.append(com) - if common_sym != [] or common_antisym != []: + if common_sym or common_antisym: result = CompWithSym(self._ring, self._frame, self._nid, self._sindex, self._output_formatter, common_sym, common_antisym) @@ -3643,11 +3653,11 @@ def __mul__(self, other): sym = list(self._sym) antisym = list(self._antisym) if isinstance(other, CompWithSym): - if other._sym != []: + if other._sym: for s in other._sym: ns = tuple(s[i]+self._nid for i in range(len(s))) sym.append(ns) - if other._antisym != []: + if other._antisym: for s in other._antisym: ns = tuple(s[i]+self._nid for i in range(len(s))) antisym.append(ns) @@ -3973,7 +3983,7 @@ def non_redundant_index_generator(self): si = self._sindex imax = self._dim - 1 + si ind = [si for k in range(self._nid)] - sym = self._sym.copy() # we may modify this in the following + sym = list(self._sym) # we may modify this in the following antisym = self._antisym for pos in range(self._nid): for isym in antisym: diff --git a/src/sage/tensor/modules/free_module_tensor.py b/src/sage/tensor/modules/free_module_tensor.py index bbcc1ecb2e0..1fa457475a0 100644 --- a/src/sage/tensor/modules/free_module_tensor.py +++ b/src/sage/tensor/modules/free_module_tensor.py @@ -563,13 +563,13 @@ def symmetries(self): elif len(self._sym) == 1: s = "symmetry: {}; ".format(self._sym[0]) else: - s = "symmetries: {}; ".format(self._sym) + s = "symmetries: {}; ".format(list(self._sym)) if len(self._antisym) == 0: a = "no antisymmetry" elif len(self._antisym) == 1: a = "antisymmetry: {}".format(self._antisym[0]) else: - a = "antisymmetries: {}".format(self._antisym) + a = "antisymmetries: {}".format(list(self._antisym)) print(s+a) #### End of simple accessors ##### From 3619b4c163234f71c4e68d4c0442e56c5f4898dc Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 29 Aug 2022 11:04:11 -0700 Subject: [PATCH 03/17] src/sage/tensor, src/sage/manifolds: Sort _sym, _antisym, store as tuples of tuples --- src/sage/manifolds/differentiable/metric.py | 8 ++-- .../manifolds/differentiable/tensorfield.py | 39 ++----------------- src/sage/tensor/modules/comp.py | 29 ++++++++------ src/sage/tensor/modules/free_module_tensor.py | 39 ++----------------- 4 files changed, 29 insertions(+), 86 deletions(-) diff --git a/src/sage/manifolds/differentiable/metric.py b/src/sage/manifolds/differentiable/metric.py index 686af0d3505..445862d6795 100644 --- a/src/sage/manifolds/differentiable/metric.py +++ b/src/sage/manifolds/differentiable/metric.py @@ -647,7 +647,7 @@ def set(self, symbiform): raise TypeError("the argument must be a tensor field") if symbiform._tensor_type != (0,2): raise TypeError("the argument must be of tensor type (0,2)") - if symbiform._sym != [(0,1)]: + if symbiform._sym != ((0,1),): raise TypeError("the argument must be symmetric") if not symbiform._domain.is_subset(self._domain): raise TypeError("the symmetric bilinear form is not defined " + @@ -2301,7 +2301,7 @@ def set(self, symbiform): "values on a parallelizable domain") if symbiform._tensor_type != (0,2): raise TypeError("the argument must be of tensor type (0,2)") - if symbiform._sym != [(0,1)]: + if symbiform._sym != ((0,1),): raise TypeError("the argument must be symmetric") if symbiform._vmodule is not self._vmodule: raise TypeError("the symmetric bilinear form and the metric are " + @@ -2781,7 +2781,7 @@ def set(self, symbiform): raise TypeError("the argument must be a tensor field") if symbiform._tensor_type != (0,2): raise TypeError("the argument must be of tensor type (0,2)") - if symbiform._sym != [(0,1)]: + if symbiform._sym != ((0,1),): raise TypeError("the argument must be symmetric") if not symbiform._domain.is_subset(self._domain): raise TypeError("the symmetric bilinear form is not defined " + @@ -3019,7 +3019,7 @@ def set(self, symbiform): "values on a parallelizable domain") if symbiform._tensor_type != (0,2): raise TypeError("the argument must be of tensor type (0,2)") - if symbiform._sym != [(0,1)]: + if symbiform._sym != ((0,1),): raise TypeError("the argument must be symmetric") if symbiform._vmodule is not self._vmodule: raise TypeError("the symmetric bilinear form and the metric are " + diff --git a/src/sage/manifolds/differentiable/tensorfield.py b/src/sage/manifolds/differentiable/tensorfield.py index 1370b104140..a2aad4d4937 100644 --- a/src/sage/manifolds/differentiable/tensorfield.py +++ b/src/sage/manifolds/differentiable/tensorfield.py @@ -58,6 +58,7 @@ from sage.rings.integer import Integer from sage.rings.integer_ring import ZZ from sage.structure.element import ModuleElementWithMutability +from sage.tensor.modules.comp import CompWithSym from sage.tensor.modules.free_module_tensor import FreeModuleTensor from sage.tensor.modules.tensor_with_indices import TensorWithIndices @@ -495,40 +496,8 @@ def __init__( self._restrictions = {} # dict. of restrictions of self on subdomains # of self._domain, with the subdomains as keys # Treatment of symmetry declarations: - self._sym = [] - if sym is not None and sym != []: - if isinstance(sym[0], (int, Integer)): - # a single symmetry is provided as a tuple -> 1-item list: - sym = [tuple(sym)] - for isym in sym: - if len(isym) > 1: - for i in isym: - if i < 0 or i > self._tensor_rank - 1: - raise IndexError("invalid position: {}".format(i) + - " not in [0,{}]".format(self._tensor_rank-1)) - self._sym.append(tuple(isym)) - self._antisym = [] - if antisym is not None and antisym != []: - if isinstance(antisym[0], (int, Integer)): - # a single antisymmetry is provided as a tuple -> 1-item list: - antisym = [tuple(antisym)] - for isym in antisym: - if len(isym) > 1: - for i in isym: - if i < 0 or i > self._tensor_rank - 1: - raise IndexError("invalid position: {}".format(i) + - " not in [0,{}]".format(self._tensor_rank-1)) - self._antisym.append(tuple(isym)) - # Final consistency check: - index_list = [] - for isym in self._sym: - index_list += isym - for isym in self._antisym: - index_list += isym - if len(index_list) != len(set(index_list)): - # There is a repeated index position: - raise IndexError("incompatible lists of symmetries: the same " + - "position appears more than once") + self._sym, self._antisym = CompWithSym._canonicalize_sym_antisym( + self._tensor_rank, sym, antisym) # Initialization of derived quantities: self._init_derived() @@ -591,7 +560,7 @@ def _repr_(self): """ # Special cases - if self._tensor_type == (0,2) and self._sym == [(0,1)]: + if self._tensor_type == (0,2) and self._sym == ((0,1),): description = "Field of symmetric bilinear forms " if self._name is not None: description += self._name + " " diff --git a/src/sage/tensor/modules/comp.py b/src/sage/tensor/modules/comp.py index d9c3dfdfe3e..3318c7b3ebf 100644 --- a/src/sage/tensor/modules/comp.py +++ b/src/sage/tensor/modules/comp.py @@ -3008,7 +3008,7 @@ def _canonicalize_sym_antisym(nb_indices, sym=None, antisym=None): sage: from sage.tensor.modules.comp import CompWithSym sage: CompWithSym._canonicalize_sym_antisym(6, [(2, 1)]) - ([(2, 1)], []) + (((1, 2),), ()) """ result_sym = [] if sym is None: @@ -3023,8 +3023,8 @@ def _canonicalize_sym_antisym(nb_indices, sym=None, antisym=None): sym = [tuple(sym)] for isym in sym: if len(isym) < 2: - raise IndexError("at least two index positions must be " + - "provided to define a symmetry") + # Drop trivial symmetry + continue for i in isym: if i < 0 or i > nb_indices - 1: raise IndexError("invalid index position: " + str(i) + @@ -3043,8 +3043,8 @@ def _canonicalize_sym_antisym(nb_indices, sym=None, antisym=None): antisym = [tuple(antisym)] for isym in antisym: if len(isym) < 2: - raise IndexError("at least two index positions must be " + - "provided to define an antisymmetry") + # Drop trivial antisymmetry + continue for i in isym: if i < 0 or i > nb_indices - 1: raise IndexError("invalid index position: " + str(i) + @@ -3060,6 +3060,11 @@ def _canonicalize_sym_antisym(nb_indices, sym=None, antisym=None): # There is a repeated index position: raise IndexError("incompatible lists of symmetries: the same " + "index position appears more than once") + # Canonicalize sort order, make tuples + result_sym = [tuple(sorted(s)) for s in result_sym] + result_antisym = [tuple(sorted(s)) for s in result_antisym] + result_sym = tuple(sorted(result_sym)) + result_antisym = tuple(sorted(result_antisym)) return result_sym, result_antisym def _repr_(self): @@ -3392,9 +3397,9 @@ def swap_adjacent_indices(self, pos1, pos2, pos3): [[0, 7, 8], [-7, 0, 9], [-8, -9, 0]]] sage: c1 = c.swap_adjacent_indices(0,1,3) sage: c._antisym # c is antisymmetric with respect to the last pair of indices... - [(1, 2)] + ((1, 2),) sage: c1._antisym #...while c1 is antisymmetric with respect to the first pair of indices - [(0, 1)] + ((0, 1),) sage: c[0,1,2] 3 sage: c1[1,2,0] @@ -3415,6 +3420,8 @@ def swap_adjacent_indices(self, pos1, pos2, pos3): for s in self._antisym: new_s = [new_lpos.index(pos) for pos in s] result._antisym.append(tuple(sorted(new_s))) + result._sym, result._antisym = self._canonicalize_sym_antisym( + self._nid, result._sym, result._antisym) # The values: for ind, val in self._comp.items(): new_ind = ind[:pos1] + ind[pos2:pos3] + ind[pos1:pos2] + ind[pos3:] @@ -4159,7 +4166,7 @@ def symmetrize(self, *pos): (0, 0, 1) ], with symmetry on the index positions (0, 1), with symmetry on the index positions (2, 3) sage: a1._sym # a1 has two distinct symmetries: - [(0, 1), (2, 3)] + ((0, 1), (2, 3)) sage: a[0,1,2,0] == a[0,0,2,1] # a is symmetric w.r.t. positions 1 and 3 True sage: a1[0,1,2,0] == a1[0,0,2,1] # a1 is not @@ -4437,10 +4444,10 @@ def antisymmetrize(self, *pos): (1, 0, 0), (0, 1, 0), (0, 0, 1) - ], with antisymmetry on the index positions (1, 3), - with antisymmetry on the index positions (0, 2) + ], with antisymmetry on the index positions (0, 2), + with antisymmetry on the index positions (1, 3) sage: s._antisym # the antisymmetry (0,1,2) has been reduced to (0,2), since 1 is involved in the new antisymmetry (1,3): - [(1, 3), (0, 2)] + ((0, 2), (1, 3)) Partial antisymmetrization of 4-indices components with a symmetry on the first two indices:: diff --git a/src/sage/tensor/modules/free_module_tensor.py b/src/sage/tensor/modules/free_module_tensor.py index 1fa457475a0..4d6d05c57b8 100644 --- a/src/sage/tensor/modules/free_module_tensor.py +++ b/src/sage/tensor/modules/free_module_tensor.py @@ -309,41 +309,8 @@ def __init__( # bases, with the bases as keys (initially empty) # Treatment of symmetry declarations: - self._sym = [] - if sym is not None and sym != []: - if isinstance(sym[0], (int, Integer)): - # a single symmetry is provided as a tuple -> 1-item list: - sym = [tuple(sym)] - for isym in sym: - if len(isym) > 1: - for i in isym: - if i<0 or i>self._tensor_rank-1: - raise IndexError("invalid position: " + str(i) + - " not in [0," + str(self._tensor_rank-1) + "]") - self._sym.append(tuple(isym)) - self._antisym = [] - if antisym is not None and antisym != []: - if isinstance(antisym[0], (int, Integer)): - # a single antisymmetry is provided as a tuple -> 1-item list: - antisym = [tuple(antisym)] - for isym in antisym: - if len(isym) > 1: - for i in isym: - if i<0 or i>self._tensor_rank-1: - raise IndexError("invalid position: " + str(i) + - " not in [0," + str(self._tensor_rank-1) + "]") - self._antisym.append(tuple(isym)) - - # Final consistency check: - index_list = [] - for isym in self._sym: - index_list += isym - for isym in self._antisym: - index_list += isym - if len(index_list) != len(set(index_list)): - # There is a repeated index position: - raise IndexError("incompatible lists of symmetries: the same " + - "position appears more than once") + self._sym, self._antisym = CompWithSym._canonicalize_sym_antisym( + self._tensor_rank, sym, antisym) # Initialization of derived quantities: FreeModuleTensor._init_derived(self) @@ -405,7 +372,7 @@ def _repr_(self): """ # Special cases - if self._tensor_type == (0,2) and self._sym == [(0,1)]: + if self._tensor_type == (0,2) and self._sym == ((0,1),): description = "Symmetric bilinear form " else: # Generic case From 970c6c88e3b1e8efc54fccff8b68a67c83bdcf5b Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 29 Aug 2022 11:50:42 -0700 Subject: [PATCH 04/17] src/sage/tensor, src/sage/manifolds: Sort _sym, _antisym, store as tuples of tuples (fixup) --- .../manifolds/differentiable/pseudo_riemannian_submanifold.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/manifolds/differentiable/pseudo_riemannian_submanifold.py b/src/sage/manifolds/differentiable/pseudo_riemannian_submanifold.py index 2eaef7510c8..3aef31eb703 100644 --- a/src/sage/manifolds/differentiable/pseudo_riemannian_submanifold.py +++ b/src/sage/manifolds/differentiable/pseudo_riemannian_submanifold.py @@ -1173,7 +1173,7 @@ def ambient_second_fundamental_form(self): g.restrict(chart.domain()).contract(pf[j]) * self.scalar_field({chart: k.comp(chart.frame())[:][i, j]}) for i in range(self._dim) for j in range(self._dim)) - gam_rst._sym = [(0, 1)] + gam_rst._sym = ((0, 1),) self._ambient_second_fundamental_form.set_restriction(gam_rst) charts = iter(self.top_charts()) From 9f15e00a4396a993fe412b41993cd542d4f177a0 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 29 Aug 2022 11:50:57 -0700 Subject: [PATCH 05/17] src/sage/tensor: Allow _sym, _antisym attributes to be tuples (fixup) --- src/sage/manifolds/differentiable/vectorfield_module.py | 8 ++++---- src/sage/tensor/modules/finite_rank_free_module.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/sage/manifolds/differentiable/vectorfield_module.py b/src/sage/manifolds/differentiable/vectorfield_module.py index cf3f1dc687d..393218bc7c0 100644 --- a/src/sage/manifolds/differentiable/vectorfield_module.py +++ b/src/sage/manifolds/differentiable/vectorfield_module.py @@ -787,7 +787,7 @@ def tensor(self, tensor_type, name=None, latex_name=None, sym=None, # a single antisymmetry is provided as a tuple or a # range object; it is converted to a 1-item list: antisym = [tuple(antisym)] - if isinstance(antisym, list): + if isinstance(antisym, (tuple, list)): antisym0 = antisym[0] else: antisym0 = antisym @@ -799,7 +799,7 @@ def tensor(self, tensor_type, name=None, latex_name=None, sym=None, # a single antisymmetry is provided as a tuple or a # range object; it is converted to a 1-item list: antisym = [tuple(antisym)] - if isinstance(antisym, list): + if isinstance(antisym, (tuple, list)): antisym0 = antisym[0] else: antisym0 = antisym @@ -2102,7 +2102,7 @@ def tensor(self, tensor_type, name=None, latex_name=None, sym=None, # a single antisymmetry is provided as a tuple or a # range object; it is converted to a 1-item list: antisym = [tuple(antisym)] - if isinstance(antisym, list): + if isinstance(antisym, (tuple, list)): antisym0 = antisym[0] else: antisym0 = antisym @@ -2114,7 +2114,7 @@ def tensor(self, tensor_type, name=None, latex_name=None, sym=None, # a single antisymmetry is provided as a tuple or a # range object; it is converted to a 1-item list: antisym = [tuple(antisym)] - if isinstance(antisym, list): + if isinstance(antisym, (tuple, list)): antisym0 = antisym[0] else: antisym0 = antisym diff --git a/src/sage/tensor/modules/finite_rank_free_module.py b/src/sage/tensor/modules/finite_rank_free_module.py index 091dc252ad5..c0692104581 100644 --- a/src/sage/tensor/modules/finite_rank_free_module.py +++ b/src/sage/tensor/modules/finite_rank_free_module.py @@ -1458,7 +1458,7 @@ def tensor(self, tensor_type, name=None, latex_name=None, sym=None, # a single antisymmetry is provided as a tuple or a range # object; it is converted to a 1-item list: antisym = [tuple(antisym)] - if isinstance(antisym, list): + if isinstance(antisym, (tuple, list)): antisym0 = antisym[0] else: antisym0 = antisym @@ -1470,7 +1470,7 @@ def tensor(self, tensor_type, name=None, latex_name=None, sym=None, # a single antisymmetry is provided as a tuple or a range # object; it is converted to a 1-item list: antisym = [tuple(antisym)] - if isinstance(antisym, list): + if isinstance(antisym, (tuple, list)): antisym0 = antisym[0] else: antisym0 = antisym From 319ecfaa335f8ba214ad59033fd91bf95579918f Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 1 Sep 2022 09:03:58 -0700 Subject: [PATCH 06/17] FiniteRankFreeModule.tensor: Use CompWithSym._canonicalize_sym_antisym --- .../tensor/modules/finite_rank_free_module.py | 27 +++++-------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/src/sage/tensor/modules/finite_rank_free_module.py b/src/sage/tensor/modules/finite_rank_free_module.py index 73b260a07c2..bb55959f4ed 100644 --- a/src/sage/tensor/modules/finite_rank_free_module.py +++ b/src/sage/tensor/modules/finite_rank_free_module.py @@ -1609,7 +1609,7 @@ def tensor(self, tensor_type, name=None, latex_name=None, sym=None, - ``latex_name`` -- (default: ``None``) string; LaTeX symbol to denote the tensor; if none is provided, the LaTeX symbol is set to ``name`` - - ``sym`` -- (default: ``None``) a symmetry or a list of symmetries + - ``sym`` -- (default: ``None``) a symmetry or an iterable of symmetries among the tensor arguments: each symmetry is described by a tuple containing the positions of the involved arguments, with the convention ``position = 0`` for the first argument. For instance: @@ -1618,7 +1618,7 @@ def tensor(self, tensor_type, name=None, latex_name=None, sym=None, * ``sym = [(0,2), (1,3,4)]`` for a symmetry between the 1st and 3rd arguments and a symmetry between the 2nd, 4th and 5th arguments. - - ``antisym`` -- (default: ``None``) antisymmetry or list of + - ``antisym`` -- (default: ``None``) antisymmetry or iterable of antisymmetries among the arguments, with the same convention as for ``sym`` @@ -1653,33 +1653,20 @@ def tensor(self, tensor_type, name=None, latex_name=None, sym=None, for more examples and documentation. """ + from .comp import CompWithSym + sym, antisym = CompWithSym._canonicalize_sym_antisym( + tensor_type[0] + tensor_type[1], sym, antisym) # Special cases: if tensor_type == (1,0): return self.element_class(self, name=name, latex_name=latex_name) elif tensor_type == (0,1): return self.linear_form(name=name, latex_name=latex_name) elif tensor_type[0] == 0 and tensor_type[1] > 1 and antisym: - if isinstance(antisym[0], (int, Integer)): - # a single antisymmetry is provided as a tuple or a range - # object; it is converted to a 1-item list: - antisym = [tuple(antisym)] - if isinstance(antisym, (tuple, list)): - antisym0 = antisym[0] - else: - antisym0 = antisym - if len(antisym0) == tensor_type[1]: + if len(antisym[0]) == tensor_type[1]: return self.alternating_form(tensor_type[1], name=name, latex_name=latex_name) elif tensor_type[0] > 1 and tensor_type[1] == 0 and antisym: - if isinstance(antisym[0], (int, Integer)): - # a single antisymmetry is provided as a tuple or a range - # object; it is converted to a 1-item list: - antisym = [tuple(antisym)] - if isinstance(antisym, (tuple, list)): - antisym0 = antisym[0] - else: - antisym0 = antisym - if len(antisym0) == tensor_type[0]: + if len(antisym[0]) == tensor_type[0]: return self.alternating_contravariant_tensor(tensor_type[0], name=name, latex_name=latex_name) # Generic case: From 8e93626a08496c71387cacd7bf9dae075803ebdc Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 1 Sep 2022 09:04:55 -0700 Subject: [PATCH 07/17] CompWithSym._canonicalize_sym_antisym: Add documentation --- src/sage/tensor/modules/comp.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/sage/tensor/modules/comp.py b/src/sage/tensor/modules/comp.py index 8e9cadbaa4a..9eea2bd1770 100644 --- a/src/sage/tensor/modules/comp.py +++ b/src/sage/tensor/modules/comp.py @@ -3000,10 +3000,28 @@ def __init__(self, ring, frame, nb_indices, start_index=0, nb_indices, sym, antisym) @staticmethod - def _canonicalize_sym_antisym(nb_indices, sym=None, antisym=None): + def _canonicalize_sym_antisym(nb_indices, sym=None, antisym=None, *, + trivial_symmetries='drop'): r""" Bring sym and antisym into their canonical form. + INPUT: + + - ``nb_indices`` -- number of integer indices labeling the components + + - ``sym`` -- (default: ``None``) a symmetry or an iterable of symmetries + among the tensor arguments: each symmetry is described by a tuple + containing the positions of the involved arguments, with the + convention ``position = 0`` for the first argument. For instance: + + * ``sym = (0,1)`` for a symmetry between the 1st and 2nd arguments + * ``sym = [(0,2), (1,3,4)]`` for a symmetry between the 1st and 3rd + arguments and a symmetry between the 2nd, 4th and 5th arguments. + + - ``antisym`` -- (default: ``None``) antisymmetry or iterable of + antisymmetries among the arguments, with the same convention + as for ``sym`` + EXAMPLES:: sage: from sage.tensor.modules.comp import CompWithSym From 564be353b347cecfd4496a169b5eaff0642de03d Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 1 Sep 2022 09:26:30 -0700 Subject: [PATCH 08/17] FiniteRankFreeModule.tensor: Restore error when trivial symmetries are passed --- src/sage/tensor/modules/comp.py | 24 ++++++++++++++----- .../tensor/modules/finite_rank_free_module.py | 3 ++- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/sage/tensor/modules/comp.py b/src/sage/tensor/modules/comp.py index 9eea2bd1770..d9a676711e1 100644 --- a/src/sage/tensor/modules/comp.py +++ b/src/sage/tensor/modules/comp.py @@ -3022,6 +3022,10 @@ def _canonicalize_sym_antisym(nb_indices, sym=None, antisym=None, *, antisymmetries among the arguments, with the same convention as for ``sym`` + - ``trivial_symmetries`` -- (default: ``"drop"``) if ``"error"``, raise + an :class:`IndexError` if any trivial symmetries are provided; + otherwise, silently drop them + EXAMPLES:: sage: from sage.tensor.modules.comp import CompWithSym @@ -3041,8 +3045,12 @@ def _canonicalize_sym_antisym(nb_indices, sym=None, antisym=None, *, sym = [tuple(sym)] for isym in sym: if len(isym) < 2: - # Drop trivial symmetry - continue + if trivial_symmetries == 'error': + raise IndexError("at least two index positions must be " + + "provided to define a symmetry") + else: + # Drop trivial symmetry + continue for i in isym: if i < 0 or i > nb_indices - 1: raise IndexError("invalid index position: " + str(i) + @@ -3053,16 +3061,20 @@ def _canonicalize_sym_antisym(nb_indices, sym=None, antisym=None, *, antisym = [] else: # Handle the case that antisym is an iterator - antisym = list(antisym) + antisym = tuple(antisym) if antisym: if isinstance(antisym[0], (int, Integer)): # a single antisymmetry is provided as a tuple or a range # object; it is converted to a 1-item list: - antisym = [tuple(antisym)] + antisym = (tuple(antisym),) for isym in antisym: if len(isym) < 2: - # Drop trivial antisymmetry - continue + if trivial_symmetries == 'error': + raise IndexError("at least two index positions must be " + + "provided to define an antisymmetry") + else: + # Drop trivial antisymmetry + continue for i in isym: if i < 0 or i > nb_indices - 1: raise IndexError("invalid index position: " + str(i) + diff --git a/src/sage/tensor/modules/finite_rank_free_module.py b/src/sage/tensor/modules/finite_rank_free_module.py index bb55959f4ed..307605b2df4 100644 --- a/src/sage/tensor/modules/finite_rank_free_module.py +++ b/src/sage/tensor/modules/finite_rank_free_module.py @@ -1655,7 +1655,8 @@ def tensor(self, tensor_type, name=None, latex_name=None, sym=None, """ from .comp import CompWithSym sym, antisym = CompWithSym._canonicalize_sym_antisym( - tensor_type[0] + tensor_type[1], sym, antisym) + tensor_type[0] + tensor_type[1], sym, antisym, + trivial_symmetries='error') # Special cases: if tensor_type == (1,0): return self.element_class(self, name=name, latex_name=latex_name) From ffea896b41332047c9f6594358b8706873023c2e Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 1 Sep 2022 09:27:11 -0700 Subject: [PATCH 09/17] CompWithSym._canonicalize_sym_antisym: Code optimization --- src/sage/tensor/modules/comp.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/sage/tensor/modules/comp.py b/src/sage/tensor/modules/comp.py index d9a676711e1..0854361076e 100644 --- a/src/sage/tensor/modules/comp.py +++ b/src/sage/tensor/modules/comp.py @@ -3032,17 +3032,20 @@ def _canonicalize_sym_antisym(nb_indices, sym=None, antisym=None, *, sage: CompWithSym._canonicalize_sym_antisym(6, [(2, 1)]) (((1, 2),), ()) """ + if not sym and not antisym: + # fast path + return (), () result_sym = [] if sym is None: - sym = [] + sym = () else: # Handle the case that sym is an iterator - sym = list(sym) + sym = tuple(sym) if sym: if isinstance(sym[0], (int, Integer)): # a single symmetry is provided as a tuple or a range object; # it is converted to a 1-item list: - sym = [tuple(sym)] + sym = (tuple(sym),) for isym in sym: if len(isym) < 2: if trivial_symmetries == 'error': @@ -3083,9 +3086,9 @@ def _canonicalize_sym_antisym(nb_indices, sym=None, antisym=None, *, # Final consistency check: index_list = [] for isym in result_sym: - index_list += isym + index_list.extend(isym) for isym in result_antisym: - index_list += isym + index_list.extend(isym) if len(index_list) != len(set(index_list)): # There is a repeated index position: raise IndexError("incompatible lists of symmetries: the same " + From fed09d56502962e93ee9f1981a3eb6af70dbe42f Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 1 Sep 2022 09:35:42 -0700 Subject: [PATCH 10/17] CompWithSym._canonicalize_sym_antisym: Sort earlier to validate indices faster --- src/sage/tensor/modules/comp.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/sage/tensor/modules/comp.py b/src/sage/tensor/modules/comp.py index 0854361076e..330fe576168 100644 --- a/src/sage/tensor/modules/comp.py +++ b/src/sage/tensor/modules/comp.py @@ -3054,11 +3054,11 @@ def _canonicalize_sym_antisym(nb_indices, sym=None, antisym=None, *, else: # Drop trivial symmetry continue - for i in isym: - if i < 0 or i > nb_indices - 1: - raise IndexError("invalid index position: " + str(i) + - " not in [0," + str(nb_indices-1) + "]") - result_sym.append(tuple(isym)) + isym = tuple(sorted(isym)) + if isym[0] < 0 or isym[-1] > nb_indices - 1: + raise IndexError("invalid index position: " + str(i) + + " not in [0," + str(nb_indices-1) + "]") + result_sym.append(isym) result_antisym = [] if antisym is None: antisym = [] @@ -3078,11 +3078,11 @@ def _canonicalize_sym_antisym(nb_indices, sym=None, antisym=None, *, else: # Drop trivial antisymmetry continue - for i in isym: - if i < 0 or i > nb_indices - 1: - raise IndexError("invalid index position: " + str(i) + - " not in [0," + str(nb_indices - 1) + "]") - result_antisym.append(tuple(isym)) + isym = tuple(sorted(isym)) + if isym[0] < 0 or isym[-1] > nb_indices - 1: + raise IndexError("invalid index position: " + str(i) + + " not in [0," + str(nb_indices - 1) + "]") + result_antisym.append(isym) # Final consistency check: index_list = [] for isym in result_sym: @@ -3094,8 +3094,6 @@ def _canonicalize_sym_antisym(nb_indices, sym=None, antisym=None, *, raise IndexError("incompatible lists of symmetries: the same " + "index position appears more than once") # Canonicalize sort order, make tuples - result_sym = [tuple(sorted(s)) for s in result_sym] - result_antisym = [tuple(sorted(s)) for s in result_antisym] result_sym = tuple(sorted(result_sym)) result_antisym = tuple(sorted(result_antisym)) return result_sym, result_antisym From 421c660af21de39acfcf53dc895d51064024fd6e Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 1 Sep 2022 09:38:59 -0700 Subject: [PATCH 11/17] src/sage/tensor/modules/finite_rank_free_module.py: Add doctest --- src/sage/tensor/modules/finite_rank_free_module.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/sage/tensor/modules/finite_rank_free_module.py b/src/sage/tensor/modules/finite_rank_free_module.py index 307605b2df4..5ffce01fe4a 100644 --- a/src/sage/tensor/modules/finite_rank_free_module.py +++ b/src/sage/tensor/modules/finite_rank_free_module.py @@ -1652,6 +1652,20 @@ def tensor(self, tensor_type, name=None, latex_name=None, sym=None, See :class:`~sage.tensor.modules.free_module_tensor.FreeModuleTensor` for more examples and documentation. + TESTS: + + Errors are raised if trivial symmetries appear in the list of symmetries or + antisymmetries. + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: M.tensor((3,0), sym=[[1]]) + Traceback (most recent call last): + ... + IndexError: at least two index positions must be provided to define a symmetry + sage: M.tensor((3,0), antisym=[[]]) + Traceback (most recent call last): + ... + IndexError: at least two index positions must be provided to define an antisymmetry """ from .comp import CompWithSym sym, antisym = CompWithSym._canonicalize_sym_antisym( From a1a3f364657633f837a59022e85f0a4ba72c2512 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 1 Sep 2022 09:49:45 -0700 Subject: [PATCH 12/17] CompWithSym.__init__: Accept keyword 'trivial_symmetries', add tests --- src/sage/tensor/modules/comp.py | 19 +++++++++++++++++-- .../tensor/modules/finite_rank_free_module.py | 2 +- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/sage/tensor/modules/comp.py b/src/sage/tensor/modules/comp.py index 330fe576168..060295be280 100644 --- a/src/sage/tensor/modules/comp.py +++ b/src/sage/tensor/modules/comp.py @@ -2983,9 +2983,24 @@ class CompWithSym(Components): sage: e + d == d + e True + TESTS: + + Errors are raised if trivial symmetries appear in the list of symmetries or + antisymmetries:: + + sage: CompWithSym(QQ, V.basis(), 3, sym=[[1]]) + Traceback (most recent call last): + ... + IndexError: at least two index positions must be provided to define a symmetry + sage: CompWithSym(QQ, V.basis(), 2, antisym=[[]]) + Traceback (most recent call last): + ... + IndexError: at least two index positions must be provided to define an antisymmetry + """ def __init__(self, ring, frame, nb_indices, start_index=0, - output_formatter=None, sym=None, antisym=None): + output_formatter=None, sym=None, antisym=None, + trivial_symmetries='error'): r""" TESTS:: @@ -2997,7 +3012,7 @@ def __init__(self, ring, frame, nb_indices, start_index=0, Components.__init__(self, ring, frame, nb_indices, start_index, output_formatter) self._sym, self._antisym = self._canonicalize_sym_antisym( - nb_indices, sym, antisym) + nb_indices, sym, antisym, trivial_symmetries=trivial_symmetries) @staticmethod def _canonicalize_sym_antisym(nb_indices, sym=None, antisym=None, *, diff --git a/src/sage/tensor/modules/finite_rank_free_module.py b/src/sage/tensor/modules/finite_rank_free_module.py index 5ffce01fe4a..889bc3fb7b0 100644 --- a/src/sage/tensor/modules/finite_rank_free_module.py +++ b/src/sage/tensor/modules/finite_rank_free_module.py @@ -1655,7 +1655,7 @@ def tensor(self, tensor_type, name=None, latex_name=None, sym=None, TESTS: Errors are raised if trivial symmetries appear in the list of symmetries or - antisymmetries. + antisymmetries:: sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: M.tensor((3,0), sym=[[1]]) From 8a2c71a6d56ca08d8fb5793ec0cd3faa4682850f Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 1 Sep 2022 09:55:28 -0700 Subject: [PATCH 13/17] VectorFieldModule.tensor, VectorFieldFreeModule.tensor: Use CompWithSym._canonicalize_sym_antisym --- .../differentiable/vectorfield_module.py | 72 ++++++------------- 1 file changed, 23 insertions(+), 49 deletions(-) diff --git a/src/sage/manifolds/differentiable/vectorfield_module.py b/src/sage/manifolds/differentiable/vectorfield_module.py index 393218bc7c0..fbdd39b7aef 100644 --- a/src/sage/manifolds/differentiable/vectorfield_module.py +++ b/src/sage/manifolds/differentiable/vectorfield_module.py @@ -773,7 +773,11 @@ def tensor(self, tensor_type, name=None, latex_name=None, sym=None, AutomorphismField from sage.manifolds.differentiable.metric import (PseudoRiemannianMetric, DegenerateMetric) - if tensor_type==(1,0): + from sage.tensor.modules.comp import CompWithSym + sym, antisym = CompWithSym._canonicalize_sym_antisym( + tensor_type[0] + tensor_type[1], sym, antisym, + trivial_symmetries='error') + if tensor_type == (1,0): return self.element_class(self, name=name, latex_name=latex_name) elif tensor_type == (0,1): @@ -783,31 +787,14 @@ def tensor(self, tensor_type, name=None, latex_name=None, sym=None, return self.automorphism(name=name, latex_name=latex_name) elif tensor_type[0] == 0 and tensor_type[1] > 1 and antisym: - if isinstance(antisym[0], (int, Integer)): - # a single antisymmetry is provided as a tuple or a - # range object; it is converted to a 1-item list: - antisym = [tuple(antisym)] - if isinstance(antisym, (tuple, list)): - antisym0 = antisym[0] - else: - antisym0 = antisym - if len(antisym0) == tensor_type[1]: + if len(antisym[0]) == tensor_type[1]: return self.alternating_form(tensor_type[1], name=name, latex_name=latex_name) elif tensor_type[0] > 1 and tensor_type[1] == 0 and antisym: - if isinstance(antisym[0], (int, Integer)): - # a single antisymmetry is provided as a tuple or a - # range object; it is converted to a 1-item list: - antisym = [tuple(antisym)] - if isinstance(antisym, (tuple, list)): - antisym0 = antisym[0] - else: - antisym0 = antisym - if len(antisym0) == tensor_type[0]: + if len(antisym[0]) == tensor_type[0]: return self.alternating_contravariant_tensor( - tensor_type[0], name=name, - latex_name=latex_name) - elif tensor_type==(0,2) and specific_type is not None: + tensor_type[0], name=name, latex_name=latex_name) + elif tensor_type == (0,2) and specific_type is not None: if issubclass(specific_type, PseudoRiemannianMetric): return self.metric(name, latex_name=latex_name) # NB: the signature is not treated @@ -816,9 +803,9 @@ def tensor(self, tensor_type, name=None, latex_name=None, sym=None, return self.metric(name, latex_name=latex_name, signature=(0, sign-1, 1)) # Generic case - return self.tensor_module(*tensor_type).element_class(self, - tensor_type, name=name, latex_name=latex_name, - sym=sym, antisym=antisym) + return self.tensor_module(*tensor_type).element_class( + self, tensor_type, name=name, latex_name=latex_name, + sym=sym, antisym=antisym) def alternating_contravariant_tensor(self, degree, name=None, latex_name=None): @@ -2088,6 +2075,10 @@ def tensor(self, tensor_type, name=None, latex_name=None, sym=None, AutomorphismField, AutomorphismFieldParal) from sage.manifolds.differentiable.metric import (PseudoRiemannianMetric, DegenerateMetric) + from sage.tensor.modules.comp import CompWithSym + sym, antisym = CompWithSym._canonicalize_sym_antisym( + tensor_type[0] + tensor_type[1], sym, antisym, + trivial_symmetries='error') if tensor_type == (1,0): return self.element_class(self, name=name, latex_name=latex_name) @@ -2098,31 +2089,14 @@ def tensor(self, tensor_type, name=None, latex_name=None, sym=None, (AutomorphismField, AutomorphismFieldParal)): return self.automorphism(name=name, latex_name=latex_name) elif tensor_type[0] == 0 and tensor_type[1] > 1 and antisym: - if isinstance(antisym[0], (int, Integer)): - # a single antisymmetry is provided as a tuple or a - # range object; it is converted to a 1-item list: - antisym = [tuple(antisym)] - if isinstance(antisym, (tuple, list)): - antisym0 = antisym[0] - else: - antisym0 = antisym - if len(antisym0) == tensor_type[1]: + if len(antisym[0]) == tensor_type[1]: return self.alternating_form(tensor_type[1], name=name, latex_name=latex_name) elif tensor_type[0] > 1 and tensor_type[1] == 0 and antisym: - if isinstance(antisym[0], (int, Integer)): - # a single antisymmetry is provided as a tuple or a - # range object; it is converted to a 1-item list: - antisym = [tuple(antisym)] - if isinstance(antisym, (tuple, list)): - antisym0 = antisym[0] - else: - antisym0 = antisym - if len(antisym0) == tensor_type[0]: + if len(antisym[0]) == tensor_type[0]: return self.alternating_contravariant_tensor( - tensor_type[0], name=name, - latex_name=latex_name) - elif tensor_type==(0,2) and specific_type is not None: + tensor_type[0], name=name, latex_name=latex_name) + elif tensor_type == (0,2) and specific_type is not None: if issubclass(specific_type, PseudoRiemannianMetric): return self.metric(name, latex_name=latex_name) # NB: the signature is not treated @@ -2131,9 +2105,9 @@ def tensor(self, tensor_type, name=None, latex_name=None, sym=None, return self.metric(name, latex_name=latex_name, signature=(0, sign-1, 1)) # Generic case - return self.tensor_module(*tensor_type).element_class(self, - tensor_type, name=name, latex_name=latex_name, - sym=sym, antisym=antisym) + return self.tensor_module(*tensor_type).element_class( + self, tensor_type, name=name, latex_name=latex_name, + sym=sym, antisym=antisym) def tensor_from_comp(self, tensor_type, comp, name=None, latex_name=None): From e34306bf0471fe2fa9da9e7b9bb86d5bf18cb8d9 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 1 Sep 2022 12:12:44 -0700 Subject: [PATCH 14/17] Revert "FiniteRankFreeModule.tensor: Restore error when trivial symmetries are passed" This reverts commit 564be353b347cecfd4496a169b5eaff0642de03d. --- src/sage/tensor/modules/comp.py | 24 +++++-------------- .../tensor/modules/finite_rank_free_module.py | 3 +-- 2 files changed, 7 insertions(+), 20 deletions(-) diff --git a/src/sage/tensor/modules/comp.py b/src/sage/tensor/modules/comp.py index 060295be280..87d2dc0b473 100644 --- a/src/sage/tensor/modules/comp.py +++ b/src/sage/tensor/modules/comp.py @@ -3037,10 +3037,6 @@ def _canonicalize_sym_antisym(nb_indices, sym=None, antisym=None, *, antisymmetries among the arguments, with the same convention as for ``sym`` - - ``trivial_symmetries`` -- (default: ``"drop"``) if ``"error"``, raise - an :class:`IndexError` if any trivial symmetries are provided; - otherwise, silently drop them - EXAMPLES:: sage: from sage.tensor.modules.comp import CompWithSym @@ -3063,12 +3059,8 @@ def _canonicalize_sym_antisym(nb_indices, sym=None, antisym=None, *, sym = (tuple(sym),) for isym in sym: if len(isym) < 2: - if trivial_symmetries == 'error': - raise IndexError("at least two index positions must be " + - "provided to define a symmetry") - else: - # Drop trivial symmetry - continue + # Drop trivial symmetry + continue isym = tuple(sorted(isym)) if isym[0] < 0 or isym[-1] > nb_indices - 1: raise IndexError("invalid index position: " + str(i) + @@ -3079,20 +3071,16 @@ def _canonicalize_sym_antisym(nb_indices, sym=None, antisym=None, *, antisym = [] else: # Handle the case that antisym is an iterator - antisym = tuple(antisym) + antisym = list(antisym) if antisym: if isinstance(antisym[0], (int, Integer)): # a single antisymmetry is provided as a tuple or a range # object; it is converted to a 1-item list: - antisym = (tuple(antisym),) + antisym = [tuple(antisym)] for isym in antisym: if len(isym) < 2: - if trivial_symmetries == 'error': - raise IndexError("at least two index positions must be " + - "provided to define an antisymmetry") - else: - # Drop trivial antisymmetry - continue + # Drop trivial antisymmetry + continue isym = tuple(sorted(isym)) if isym[0] < 0 or isym[-1] > nb_indices - 1: raise IndexError("invalid index position: " + str(i) + diff --git a/src/sage/tensor/modules/finite_rank_free_module.py b/src/sage/tensor/modules/finite_rank_free_module.py index 889bc3fb7b0..442b5576b49 100644 --- a/src/sage/tensor/modules/finite_rank_free_module.py +++ b/src/sage/tensor/modules/finite_rank_free_module.py @@ -1669,8 +1669,7 @@ def tensor(self, tensor_type, name=None, latex_name=None, sym=None, """ from .comp import CompWithSym sym, antisym = CompWithSym._canonicalize_sym_antisym( - tensor_type[0] + tensor_type[1], sym, antisym, - trivial_symmetries='error') + tensor_type[0] + tensor_type[1], sym, antisym) # Special cases: if tensor_type == (1,0): return self.element_class(self, name=name, latex_name=latex_name) From c6b6b2ed6ddc44044b8e0915d768cd22672f0dab Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 1 Sep 2022 12:16:19 -0700 Subject: [PATCH 15/17] src/sage/tensor, src/sage/manifolds: Remove parameter 'trivial_symmetries' again --- .../differentiable/vectorfield_module.py | 6 ++--- src/sage/tensor/modules/comp.py | 23 +++---------------- .../tensor/modules/finite_rank_free_module.py | 12 ++++------ 3 files changed, 9 insertions(+), 32 deletions(-) diff --git a/src/sage/manifolds/differentiable/vectorfield_module.py b/src/sage/manifolds/differentiable/vectorfield_module.py index fbdd39b7aef..1e097907967 100644 --- a/src/sage/manifolds/differentiable/vectorfield_module.py +++ b/src/sage/manifolds/differentiable/vectorfield_module.py @@ -775,8 +775,7 @@ def tensor(self, tensor_type, name=None, latex_name=None, sym=None, DegenerateMetric) from sage.tensor.modules.comp import CompWithSym sym, antisym = CompWithSym._canonicalize_sym_antisym( - tensor_type[0] + tensor_type[1], sym, antisym, - trivial_symmetries='error') + tensor_type[0] + tensor_type[1], sym, antisym) if tensor_type == (1,0): return self.element_class(self, name=name, latex_name=latex_name) @@ -2077,8 +2076,7 @@ def tensor(self, tensor_type, name=None, latex_name=None, sym=None, DegenerateMetric) from sage.tensor.modules.comp import CompWithSym sym, antisym = CompWithSym._canonicalize_sym_antisym( - tensor_type[0] + tensor_type[1], sym, antisym, - trivial_symmetries='error') + tensor_type[0] + tensor_type[1], sym, antisym) if tensor_type == (1,0): return self.element_class(self, name=name, latex_name=latex_name) diff --git a/src/sage/tensor/modules/comp.py b/src/sage/tensor/modules/comp.py index 87d2dc0b473..0bc56fe728c 100644 --- a/src/sage/tensor/modules/comp.py +++ b/src/sage/tensor/modules/comp.py @@ -2982,25 +2982,9 @@ class CompWithSym(Components): ) sage: e + d == d + e True - - TESTS: - - Errors are raised if trivial symmetries appear in the list of symmetries or - antisymmetries:: - - sage: CompWithSym(QQ, V.basis(), 3, sym=[[1]]) - Traceback (most recent call last): - ... - IndexError: at least two index positions must be provided to define a symmetry - sage: CompWithSym(QQ, V.basis(), 2, antisym=[[]]) - Traceback (most recent call last): - ... - IndexError: at least two index positions must be provided to define an antisymmetry - """ def __init__(self, ring, frame, nb_indices, start_index=0, - output_formatter=None, sym=None, antisym=None, - trivial_symmetries='error'): + output_formatter=None, sym=None, antisym=None): r""" TESTS:: @@ -3012,11 +2996,10 @@ def __init__(self, ring, frame, nb_indices, start_index=0, Components.__init__(self, ring, frame, nb_indices, start_index, output_formatter) self._sym, self._antisym = self._canonicalize_sym_antisym( - nb_indices, sym, antisym, trivial_symmetries=trivial_symmetries) + nb_indices, sym, antisym) @staticmethod - def _canonicalize_sym_antisym(nb_indices, sym=None, antisym=None, *, - trivial_symmetries='drop'): + def _canonicalize_sym_antisym(nb_indices, sym=None, antisym=None): r""" Bring sym and antisym into their canonical form. diff --git a/src/sage/tensor/modules/finite_rank_free_module.py b/src/sage/tensor/modules/finite_rank_free_module.py index 442b5576b49..53f1aa054b0 100644 --- a/src/sage/tensor/modules/finite_rank_free_module.py +++ b/src/sage/tensor/modules/finite_rank_free_module.py @@ -1654,18 +1654,14 @@ def tensor(self, tensor_type, name=None, latex_name=None, sym=None, TESTS: - Errors are raised if trivial symmetries appear in the list of symmetries or - antisymmetries:: + Trivial symmetries in the list of symmetries or antisymmetries are silently + ignored:: sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: M.tensor((3,0), sym=[[1]]) - Traceback (most recent call last): - ... - IndexError: at least two index positions must be provided to define a symmetry + Type-(3,0) tensor on the Rank-3 free module M over the Integer Ring sage: M.tensor((3,0), antisym=[[]]) - Traceback (most recent call last): - ... - IndexError: at least two index positions must be provided to define an antisymmetry + Type-(3,0) tensor on the Rank-3 free module M over the Integer Ring """ from .comp import CompWithSym sym, antisym = CompWithSym._canonicalize_sym_antisym( From bec2f6540e63cca9b1c192ceb9ea1c15b7aee762 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 2 Sep 2022 09:14:16 -0700 Subject: [PATCH 16/17] CompWithSym._canonicalize_sym_antisym: Refactor, fix index validation, add tests --- src/sage/tensor/modules/comp.py | 98 ++++++++++++++++++--------------- 1 file changed, 54 insertions(+), 44 deletions(-) diff --git a/src/sage/tensor/modules/comp.py b/src/sage/tensor/modules/comp.py index 0bc56fe728c..dec039e5d1c 100644 --- a/src/sage/tensor/modules/comp.py +++ b/src/sage/tensor/modules/comp.py @@ -2998,10 +2998,61 @@ def __init__(self, ring, frame, nb_indices, start_index=0, self._sym, self._antisym = self._canonicalize_sym_antisym( nb_indices, sym, antisym) + @staticmethod + def _canonicalize_sym_or_antisym(nb_indices, sym_or_antisym): + r""" + Bring ``sym`` or ``antisym`` to its canonical form. + + INPUT: + + - ``nb_indices`` -- number of integer indices labeling the components + + - ``sym_or_antisym`` -- (default: ``None``) a symmetry/antisymmetry + or an iterable of symmetries or an iterable of antisymmetries + among the tensor arguments: each symmetry is described by a tuple + containing the positions of the involved arguments, with the + convention ``position = 0`` for the first argument. For instance: + + TESTS:: + + sage: from sage.tensor.modules.comp import CompWithSym + sage: CompWithSym._canonicalize_sym_or_antisym(3, [0, -1]) + Traceback (most recent call last): + ... + IndexError: invalid index position: -1 not in [0,2] + sage: CompWithSym._canonicalize_sym_or_antisym(3, [3, 1]) + Traceback (most recent call last): + ... + IndexError: invalid index position: 3 not in [0,2] + """ + if not sym_or_antisym: + return () + # Handle the case that sym_or_antisym is an iterator + sym_or_antisym = tuple(sym_or_antisym) + result_sym_or_antisym = [] + if isinstance(sym_or_antisym[0], (int, Integer)): + # a single symmetry is provided as a tuple or a range object; + # it is converted to a 1-item list: + sym_or_antisym = (tuple(sym_or_antisym),) + for isym in sym_or_antisym: + if len(isym) < 2: + # Drop trivial symmetry + continue + isym = tuple(sorted(isym)) + if isym[0] < 0: + raise IndexError("invalid index position: " + str(isym[0]) + + " not in [0," + str(nb_indices-1) + "]") + if isym[-1] > nb_indices - 1: + raise IndexError("invalid index position: " + str(isym[-1]) + + " not in [0," + str(nb_indices-1) + "]") + result_sym_or_antisym.append(isym) + # Canonicalize sort order, make tuples + return tuple(sorted(result_sym_or_antisym)) + @staticmethod def _canonicalize_sym_antisym(nb_indices, sym=None, antisym=None): r""" - Bring sym and antisym into their canonical form. + Bring ``sym`` and ``antisym`` into their canonical form. INPUT: @@ -3029,46 +3080,8 @@ def _canonicalize_sym_antisym(nb_indices, sym=None, antisym=None): if not sym and not antisym: # fast path return (), () - result_sym = [] - if sym is None: - sym = () - else: - # Handle the case that sym is an iterator - sym = tuple(sym) - if sym: - if isinstance(sym[0], (int, Integer)): - # a single symmetry is provided as a tuple or a range object; - # it is converted to a 1-item list: - sym = (tuple(sym),) - for isym in sym: - if len(isym) < 2: - # Drop trivial symmetry - continue - isym = tuple(sorted(isym)) - if isym[0] < 0 or isym[-1] > nb_indices - 1: - raise IndexError("invalid index position: " + str(i) + - " not in [0," + str(nb_indices-1) + "]") - result_sym.append(isym) - result_antisym = [] - if antisym is None: - antisym = [] - else: - # Handle the case that antisym is an iterator - antisym = list(antisym) - if antisym: - if isinstance(antisym[0], (int, Integer)): - # a single antisymmetry is provided as a tuple or a range - # object; it is converted to a 1-item list: - antisym = [tuple(antisym)] - for isym in antisym: - if len(isym) < 2: - # Drop trivial antisymmetry - continue - isym = tuple(sorted(isym)) - if isym[0] < 0 or isym[-1] > nb_indices - 1: - raise IndexError("invalid index position: " + str(i) + - " not in [0," + str(nb_indices - 1) + "]") - result_antisym.append(isym) + result_sym = CompWithSym._canonicalize_sym_or_antisym(nb_indices, sym) + result_antisym = CompWithSym._canonicalize_sym_or_antisym(nb_indices, antisym) # Final consistency check: index_list = [] for isym in result_sym: @@ -3079,9 +3092,6 @@ def _canonicalize_sym_antisym(nb_indices, sym=None, antisym=None): # There is a repeated index position: raise IndexError("incompatible lists of symmetries: the same " + "index position appears more than once") - # Canonicalize sort order, make tuples - result_sym = tuple(sorted(result_sym)) - result_antisym = tuple(sorted(result_antisym)) return result_sym, result_antisym def _repr_(self): From 66009e7700f8fc0610dc494a09089070bb6f609a Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 2 Sep 2022 09:24:03 -0700 Subject: [PATCH 17/17] src/sage/tensor/modules/comp.py: Fix docstring --- src/sage/tensor/modules/comp.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sage/tensor/modules/comp.py b/src/sage/tensor/modules/comp.py index dec039e5d1c..40f2cf292c7 100644 --- a/src/sage/tensor/modules/comp.py +++ b/src/sage/tensor/modules/comp.py @@ -3009,9 +3009,9 @@ def _canonicalize_sym_or_antisym(nb_indices, sym_or_antisym): - ``sym_or_antisym`` -- (default: ``None``) a symmetry/antisymmetry or an iterable of symmetries or an iterable of antisymmetries - among the tensor arguments: each symmetry is described by a tuple - containing the positions of the involved arguments, with the - convention ``position = 0`` for the first argument. For instance: + among the tensor arguments: each symmetry/antisymmetry is described + by a tuple containing the positions of the involved arguments, with + the convention ``position = 0`` for the first argument. TESTS::