diff --git a/src/sage/categories/pushout.py b/src/sage/categories/pushout.py index c48ec6ec672..ea4e3c22dcc 100644 --- a/src/sage/categories/pushout.py +++ b/src/sage/categories/pushout.py @@ -1822,7 +1822,8 @@ class VectorFunctor(ConstructionFunctor): rank = 10 # ranking of functor, not rank of module. # This coincides with the rank of the matrix construction functor, but this is OK since they cannot both be applied in any order - def __init__(self, n, is_sparse=False, inner_product_matrix=None): + def __init__(self, n=None, is_sparse=False, inner_product_matrix=None, *, + with_basis='standard', basis_keys=None, name_mapping=None, latex_name_mapping=None): """ INPUT: @@ -1830,6 +1831,8 @@ def __init__(self, n, is_sparse=False, inner_product_matrix=None): - ``is_sparse`` (optional bool, default ``False``), create sparse implementation of modules - ``inner_product_matrix``: ``n`` by ``n`` matrix, used to compute inner products in the to-be-created modules + - ``name_mapping``, ``latex_name_mapping``: Dictionaries from base rings to names + - other keywords: see :func:`~sage.modules.free_module.FreeModule` TESTS:: @@ -1860,14 +1863,22 @@ def __init__(self, n, is_sparse=False, inner_product_matrix=None): self.n = n self.is_sparse = is_sparse self.inner_product_matrix = inner_product_matrix + self.with_basis = with_basis + self.basis_keys = basis_keys + if name_mapping is None: + name_mapping = {} + self.name_mapping = name_mapping + if latex_name_mapping is None: + latex_name_mapping = {} + self.latex_name_mapping = latex_name_mapping def _apply_functor(self, R): - """ + r""" Apply the functor to an object of ``self``'s domain. TESTS:: - sage: from sage.categories.pushout import VectorFunctor + sage: from sage.categories.pushout import VectorFunctor, pushout sage: F1 = VectorFunctor(3, inner_product_matrix = Matrix(3,3,range(9))) sage: M1 = F1(ZZ) # indirect doctest sage: M1.is_sparse() @@ -1885,9 +1896,31 @@ def _apply_functor(self, R): sage: v.inner_product(v) 14 + sage: M = FreeModule(ZZ, 4, with_basis=None, name='M') + sage: latex(M) + M + sage: M_QQ = pushout(M, QQ) + sage: latex(M_QQ) + M \otimes \Bold{Q} + """ from sage.modules.free_module import FreeModule - return FreeModule(R, self.n, sparse=self.is_sparse, inner_product_matrix=self.inner_product_matrix) + name = self.name_mapping.get(R, None) + latex_name = self.latex_name_mapping.get(R, None) + if name is None: + for base_ring, name in self.name_mapping.items(): + name = f'{name}_base_ext' + break + if latex_name is None: + from sage.misc.latex import latex + for base_ring, latex_name in self.latex_name_mapping.items(): + latex_name = fr'{latex_name} \otimes {latex(R)}' + break + if name is None and latex_name is None: + return FreeModule(R, self.n, sparse=self.is_sparse, inner_product_matrix=self.inner_product_matrix, + with_basis=self.with_basis, basis_keys=self.basis_keys) + return FreeModule(R, self.n, sparse=self.is_sparse, inner_product_matrix=self.inner_product_matrix, + with_basis=self.with_basis, basis_keys=self.basis_keys, name=name, latex_name=latex_name) def _apply_functor_to_morphism(self, f): """ @@ -1924,7 +1957,11 @@ def __eq__(self, other): """ if isinstance(other, VectorFunctor): return (self.n == other.n and - self.inner_product_matrix == other.inner_product_matrix) + self.inner_product_matrix == other.inner_product_matrix and + self.with_basis == other.with_basis and + self.basis_keys == other.basis_keys and + self.name_mapping == other.name_mapping and + self.latex_name_mapping == other.latex_name_mapping) return False def __ne__(self, other): @@ -1997,19 +2034,73 @@ def merge(self, other): [3 4 5] [6 7 8]' + Names are removed when they conflict:: + + sage: from sage.categories.pushout import VectorFunctor, pushout + sage: M_ZZx = FreeModule(ZZ['x'], 4, with_basis=None, name='M_ZZx') + sage: N_ZZx = FreeModule(ZZ['x'], 4, with_basis=None, name='N_ZZx') + sage: pushout(M_ZZx, QQ) + Rank-4 free module M_ZZx_base_ext over the Univariate Polynomial Ring in x over Rational Field + sage: pushout(M_ZZx, N_ZZx) + Rank-4 free module over the Univariate Polynomial Ring in x over Integer Ring + sage: pushout(pushout(M_ZZx, N_ZZx), QQ) + Rank-4 free module over the Univariate Polynomial Ring in x over Rational Field """ if not isinstance(other, VectorFunctor): return None + + if self.with_basis != other.with_basis: + return None + else: + with_basis = self.with_basis + + if self.basis_keys != other.basis_keys: + # TODO: If both are enumerated families, should we try to take the union of the families? + return None + else: + basis_keys = self.basis_keys + + is_sparse = self.is_sparse and other.is_sparse + if self.inner_product_matrix is None: - return VectorFunctor(self.n, self.is_sparse and other.is_sparse, other.inner_product_matrix) - if other.inner_product_matrix is None: - return VectorFunctor(self.n, self.is_sparse and other.is_sparse, self.inner_product_matrix) - # At this point, we know that the user wants to take care of the inner product. - # So, we only merge if both coincide: - if self.inner_product_matrix != other.inner_product_matrix: + inner_product_matrix = other.inner_product_matrix + elif other.inner_product_matrix is None: + inner_product_matrix = self.inner_product_matrix + elif self.inner_product_matrix != other.inner_product_matrix: + # At this point, we know that the user wants to take care of the inner product. + # So, we only merge if both coincide: + return None + else: + inner_product_matrix = None + + if self.n != other.n: return None else: - return VectorFunctor(self.n, self.is_sparse and other.is_sparse, self.inner_product_matrix) + n = self.n + + name_mapping = dict() + for base_ring, name in self.name_mapping.items(): + try: + other_name = other.name_mapping[base_ring] + except KeyError: + name_mapping[base_ring] = name + else: + if name == other_name: + name_mapping[base_ring] = name + + latex_name_mapping = dict() + for base_ring, latex_name in self.latex_name_mapping.items(): + try: + other_latex_name = other.latex_name_mapping[base_ring] + except KeyError: + latex_name_mapping[base_ring] = latex_name + else: + if latex_name == other_latex_name: + latex_name_mapping[base_ring] = latex_name + + return VectorFunctor(n, is_sparse, inner_product_matrix, + with_basis=with_basis, basis_keys=basis_keys, + name_mapping=name_mapping, latex_name_mapping=latex_name_mapping) class SubspaceFunctor(ConstructionFunctor): diff --git a/src/sage/categories/sets_cat.py b/src/sage/categories/sets_cat.py index 09a57770a3b..0cc9cd34f4f 100644 --- a/src/sage/categories/sets_cat.py +++ b/src/sage/categories/sets_cat.py @@ -2500,6 +2500,19 @@ def cartesian_projection(self, i): 42 """ + def construction(self): + """ + The construction functor and the list of Cartesian factors. + + EXAMPLES:: + + sage: C = cartesian_product([QQ, ZZ, ZZ]) + sage: C.construction() + (The cartesian_product functorial construction, + (Rational Field, Integer Ring, Integer Ring)) + """ + return cartesian_product, self.cartesian_factors() + @abstract_method def _cartesian_product_of_elements(self, elements): """ diff --git a/src/sage/combinat/free_module.py b/src/sage/combinat/free_module.py index a2f789a4202..f11f9b81499 100644 --- a/src/sage/combinat/free_module.py +++ b/src/sage/combinat/free_module.py @@ -290,8 +290,9 @@ def __classcall_private__(cls, base_ring, basis_keys=None, category=None, sage: F3 = CombinatorialFreeModule(QQ, ['a','b'], category = (ModulesWithBasis(QQ),)) sage: F4 = CombinatorialFreeModule(QQ, ['a','b'], category = (ModulesWithBasis(QQ),CommutativeAdditiveSemigroups())) sage: F5 = CombinatorialFreeModule(QQ, ['a','b'], category = (ModulesWithBasis(QQ),Category.join((LeftModules(QQ), RightModules(QQ))))) - sage: F1 is F, F2 is F, F3 is F, F4 is F, F5 is F - (True, True, True, True, True) + sage: F6 = CombinatorialFreeModule(QQ, ['a','b'], category=ModulesWithBasis(QQ).FiniteDimensional()) + sage: F1 is F, F2 is F, F3 is F, F4 is F, F5 is F, F6 is F + (True, True, True, True, True, True) sage: G = CombinatorialFreeModule(QQ, ['a','b'], category = AlgebrasWithBasis(QQ)) sage: F is G @@ -302,6 +303,8 @@ def __classcall_private__(cls, base_ring, basis_keys=None, category=None, if isinstance(basis_keys, (list, tuple)): basis_keys = FiniteEnumeratedSet(basis_keys) category = ModulesWithBasis(base_ring).or_subcategory(category) + if basis_keys in Sets().Finite(): + category = category.FiniteDimensional() # bracket or latex_bracket might be lists, so convert # them to tuples so that they're hashable. bracket = keywords.get('bracket', None) @@ -458,6 +461,27 @@ def __init__(self, R, basis_keys=None, element_class=None, category=None, self._order = None + def construction(self): + """ + The construction functor and base ring for self. + + EXAMPLES:: + + sage: F = CombinatorialFreeModule(QQ, ['a','b','c'], category=AlgebrasWithBasis(QQ)) + sage: F.construction() + (VectorFunctor, Rational Field) + """ + # Try to take it from the category + c = super().construction() + if c is not None: + return c + if self.__class__.__base__ != CombinatorialFreeModule: + # The construction is not suitable for subclasses + return None + from sage.categories.pushout import VectorFunctor + return VectorFunctor(None, True, None, with_basis='standard', + basis_keys=self.basis().keys()), self.base_ring() + # For backwards compatibility _repr_term = IndexedGenerators._repr_generator _latex_term = IndexedGenerators._latex_generator diff --git a/src/sage/manifolds/differentiable/tangent_space.py b/src/sage/manifolds/differentiable/tangent_space.py index 0825f867e5e..36b68c7b5c5 100644 --- a/src/sage/manifolds/differentiable/tangent_space.py +++ b/src/sage/manifolds/differentiable/tangent_space.py @@ -252,6 +252,19 @@ def __init__(self, point): pass self._basis_changes[(basis1, basis2)] = auto + def construction(self): + """ + TESTS:: + + sage: M = Manifold(2, 'M') + sage: X. = M.chart() + sage: p = M.point((3,-2), name='p') + sage: Tp = M.tangent_space(p) + sage: Tp.construction() is None + True + """ + return None + def _repr_(self): r""" String representation of ``self``. diff --git a/src/sage/manifolds/vector_bundle_fiber.py b/src/sage/manifolds/vector_bundle_fiber.py index a79d42176e4..90a59e97169 100644 --- a/src/sage/manifolds/vector_bundle_fiber.py +++ b/src/sage/manifolds/vector_bundle_fiber.py @@ -242,6 +242,19 @@ def __init__(self, vector_bundle, point): pass self._basis_changes[(basis1, basis2)] = auto + def construction(self): + r""" + TESTS:: + + sage: M = Manifold(3, 'M', structure='top') + sage: X. = M.chart() + sage: p = M((0,0,0), name='p') + sage: E = M.vector_bundle(2, 'E') + sage: E.fiber(p).construction() is None + True + """ + return None + def _repr_(self): r""" String representation of ``self``. diff --git a/src/sage/modules/free_module.py b/src/sage/modules/free_module.py index 9946a588c85..b661c44b9a7 100644 --- a/src/sage/modules/free_module.py +++ b/src/sage/modules/free_module.py @@ -471,6 +471,21 @@ def FreeModule(base_ring, rank_or_basis_keys=None, sparse=False, inner_product_m sage: _.category() Category of finite dimensional vector spaces over Rational Field + sage: FreeModule(QQ, [1, 2, 3, 4], with_basis=None) + 4-dimensional vector space over the Rational Field + sage: _.category() + Category of finite dimensional vector spaces over Rational Field + + TESTS:: + + sage: FreeModule(QQ, ['a', 2, 3, 4], with_basis=None) + Traceback (most recent call last): + ... + NotImplementedError: FiniteRankFreeModule only supports integer ranges as basis_keys, got ['a', 2, 3, 4] + sage: FreeModule(QQ, [1, 3, 5], with_basis=None) + Traceback (most recent call last): + ... + NotImplementedError: FiniteRankFreeModule only supports integer ranges as basis_keys, got [1, 3, 5] """ if rank_or_basis_keys is not None: try: @@ -481,6 +496,19 @@ def FreeModule(base_ring, rank_or_basis_keys=None, sparse=False, inner_product_m if inner_product_matrix is not None: raise NotImplementedError from sage.tensor.modules.finite_rank_free_module import FiniteRankFreeModule + if basis_keys: + if not all(key in sage.rings.integer_ring.ZZ for key in basis_keys): + raise NotImplementedError(f'FiniteRankFreeModule only supports integer ranges as basis_keys, got {basis_keys}') + start_index = min(basis_keys) + end_index = max(basis_keys) + rank = end_index - start_index + 1 + # Check that the ordered list of basis_keys is the range from start_index to end_index + if (len(basis_keys) != rank + or not all(key == index + for key, index in zip(basis_keys, + range(start_index, end_index + 1)))): + raise NotImplementedError(f'FiniteRankFreeModule only supports integer ranges as basis_keys, got {basis_keys}') + return FiniteRankFreeModule(base_ring, rank, start_index=start_index, **args) return FiniteRankFreeModule(base_ring, rank, **args) elif with_basis == 'standard': if rank is not None: diff --git a/src/sage/tensor/modules/ext_pow_free_module.py b/src/sage/tensor/modules/ext_pow_free_module.py index c68033fdf51..83b0d5b317f 100644 --- a/src/sage/tensor/modules/ext_pow_free_module.py +++ b/src/sage/tensor/modules/ext_pow_free_module.py @@ -253,6 +253,21 @@ def __init__(self, fmodule, degree, name=None, latex_name=None): output_formatter=fmodule._output_formatter) fmodule._all_modules.add(self) + def construction(self): + r""" + TESTS:: + + sage: from sage.tensor.modules.ext_pow_free_module import ExtPowerFreeModule + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: A = ExtPowerFreeModule(M, 2) + sage: A.construction() is None + True + """ + # No construction until https://trac.sagemath.org/ticket/30242 + # makes this a quotient of TensorFreeModule + return None + #### Parent methods def _element_constructor_(self, comp=[], basis=None, name=None, @@ -654,6 +669,21 @@ def __init__(self, fmodule, degree, name=None, latex_name=None): output_formatter=fmodule._output_formatter) fmodule._all_modules.add(self) + def construction(self): + r""" + TESTS:: + + sage: from sage.tensor.modules.ext_pow_free_module import ExtPowerDualFreeModule + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: A = ExtPowerDualFreeModule(M, 2) + sage: A.construction() is None + True + """ + # No construction until https://trac.sagemath.org/ticket/30242 + # makes this a quotient of TensorFreeModule + return None + #### Parent methods def _element_constructor_(self, comp=[], basis=None, name=None, diff --git a/src/sage/tensor/modules/finite_rank_free_module.py b/src/sage/tensor/modules/finite_rank_free_module.py index 091dc252ad5..b2182291f5d 100644 --- a/src/sage/tensor/modules/finite_rank_free_module.py +++ b/src/sage/tensor/modules/finite_rank_free_module.py @@ -845,6 +845,39 @@ def __init__( self._general_linear_group = None # to be set by # self.general_linear_group() + def construction(self): + """ + The construction functor and base ring for self. + + EXAMPLES:: + + sage: FiniteRankFreeModule._clear_cache_() # for doctests only + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: M.construction() + (VectorFunctor, Integer Ring) + sage: N = FiniteRankFreeModule(ZZ, 3, name='N', start_index=17) + sage: N.construction() + (VectorFunctor, Integer Ring) + """ + # Try to take it from the category + c = super().construction() + if c is not None: + return c + # Implementation restrictions: + if self._output_formatter: + return None + from sage.categories.pushout import VectorFunctor + kwds = dict(is_sparse=False, + inner_product_matrix=None, + with_basis=None, + name_mapping={self.base_ring(): self._name} if self._name else None, + latex_name_mapping={self.base_ring(): self._latex_name} if self._latex_name else None) + if self._sindex: + return (VectorFunctor(basis_keys=list(self.irange()), **kwds), + self.base_ring()) + return (VectorFunctor(n=self.rank(), **kwds), + self.base_ring()) + #### Parent methods def _element_constructor_(self, comp=[], basis=None, name=None, diff --git a/src/sage/tensor/modules/tensor_free_module.py b/src/sage/tensor/modules/tensor_free_module.py index dbeb1f2be25..ded223bd2bf 100644 --- a/src/sage/tensor/modules/tensor_free_module.py +++ b/src/sage/tensor/modules/tensor_free_module.py @@ -393,6 +393,18 @@ def __init__(self, fmodule, tensor_type, name=None, latex_name=None): output_formatter=fmodule._output_formatter) fmodule._all_modules.add(self) + def construction(self): + r""" + TESTS:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: T = M.tensor_module(2, 3) + sage: T.construction() is None + True + """ + # No construction until https://trac.sagemath.org/ticket/31276 provides tensor_product methods + return None + #### Parent Methods def _element_constructor_(self, comp=[], basis=None, name=None,