From 57af00f376e888da05ed796b5f4c0873495c68c8 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 29 Feb 2024 22:09:35 -0800 Subject: [PATCH 1/2] sage.categories.morphism.SetIsomorphism: New --- src/sage/categories/map.pyx | 3 +- src/sage/categories/morphism.pxd | 3 + src/sage/categories/morphism.pyx | 158 ++++++++++++++++++++++++++++++- 3 files changed, 162 insertions(+), 2 deletions(-) diff --git a/src/sage/categories/map.pyx b/src/sage/categories/map.pyx index b41b7538c59..a3355563d34 100644 --- a/src/sage/categories/map.pyx +++ b/src/sage/categories/map.pyx @@ -1272,7 +1272,7 @@ cdef class Map(Element): def section(self): """ - Return a section of self. + Return a section of ``self``. .. NOTE:: @@ -1439,6 +1439,7 @@ cdef class Section(Map): """ return self._inverse + cdef class FormalCompositeMap(Map): """ Formal composite maps. diff --git a/src/sage/categories/morphism.pxd b/src/sage/categories/morphism.pxd index e5befc8207e..52847fd83b7 100644 --- a/src/sage/categories/morphism.pxd +++ b/src/sage/categories/morphism.pxd @@ -8,3 +8,6 @@ cdef class Morphism(Map): cdef class SetMorphism(Morphism): cdef object _function cpdef bint _eq_c_impl(left, Element right) noexcept + +cdef class SetIsomorphism(SetMorphism): + cdef object _inverse diff --git a/src/sage/categories/morphism.pyx b/src/sage/categories/morphism.pyx index 0e4805ef1b4..d4b412dd126 100644 --- a/src/sage/categories/morphism.pyx +++ b/src/sage/categories/morphism.pyx @@ -570,7 +570,7 @@ cdef class SetMorphism(Morphism): - ``parent`` -- a Homset - ``function`` -- a Python function that takes elements - of the domain as input and returns elements of the domain. + of the domain as input and returns elements of the codomain. EXAMPLES:: @@ -736,3 +736,159 @@ cdef class SetMorphism(Morphism): return not (isinstance(right, Element) and self._eq_c_impl(right)) else: return False + + +cdef class SetIsomorphism(SetMorphism): + r""" + An isomorphism of sets. + + INPUT: + + - ``parent`` -- a Homset + - ``function`` -- a Python function that takes elements + of the domain as input and returns elements of the codomain. + + EXAMPLES:: + + sage: f = sage.categories.morphism.SetIsomorphism(Hom(ZZ, ZZ, Sets()), + ....: operator.__neg__); f + Generic endomorphism of Integer Ring + sage: f._set_inverse(f) + sage: ~f is f + True + """ + def _set_inverse(self, inverse): + r""" + Set the inverse morphism of ``self`` to be ``inverse``. + + INPUT: + + - ``inverse`` -- a :class:`SetIsomorphism` + + EXAMPLES:: + + sage: f = sage.categories.morphism.SetIsomorphism(Hom(ZZ, ZZ, Sets()), + ....: operator.__neg__) + sage: f._set_inverse(f) + sage: ~f is f + True + """ + self._inverse = inverse + + def __invert__(self): + r""" + Return the inverse morphism of ``self``. + + If :meth:`_set_inverse` has not been called yet, an error is raised. + + EXAMPLES:: + + sage: f = sage.categories.morphism.SetIsomorphism(Hom(ZZ, ZZ, Sets()), + ....: operator.__neg__) + sage: ~f + Traceback (most recent call last): + ... + RuntimeError: inverse morphism has not been set + sage: f._set_inverse(f) + sage: ~f + Generic endomorphism of Integer Ring + """ + if not self._inverse: + raise RuntimeError('inverse morphism has not been set') + return self._inverse + + cdef dict _extra_slots(self) noexcept: + """ + Extend the dictionary with extra slots for this class. + + INPUT: + + - ``_slots`` -- a dictionary + + EXAMPLES:: + + sage: f = sage.categories.morphism.SetIsomorphism(Hom(ZZ, ZZ, Sets()), + ....: operator.__neg__) + sage: f._set_inverse(f) + sage: f._extra_slots_test() + {'_codomain': Integer Ring, + '_domain': Integer Ring, + '_function': , + '_inverse': Generic endomorphism of Integer Ring, + '_is_coercion': False, + '_repr_type_str': None} + """ + slots = SetMorphism._extra_slots(self) + slots['_inverse'] = self._inverse + return slots + + cdef _update_slots(self, dict _slots) noexcept: + """ + Update the slots of ``self`` from the data in the dictionary. + + INPUT: + + - ``_slots`` -- a dictionary + + EXAMPLES:: + + sage: f = sage.categories.morphism.SetIsomorphism(Hom(ZZ, ZZ, Sets()), + ....: operator.__neg__) + sage: f._update_slots_test({'_function': operator.__neg__, + ....: '_inverse': f, + ....: '_domain': QQ, + ....: '_codomain': QQ, + ....: '_repr_type_str': 'bla'}) + sage: f(3) + -3 + sage: f._repr_type() + 'bla' + sage: f.domain() + Rational Field + sage: f.codomain() + Rational Field + sage: f.inverse() == f + True + """ + self._inverse = _slots['_inverse'] + SetMorphism._update_slots(self, _slots) + + def section(self): + """ + Return a section of this morphism. + + EXAMPLES:: + + sage: f = sage.categories.morphism.SetIsomorphism(Hom(ZZ, ZZ, Sets()), + ....: operator.__neg__) + sage: f._set_inverse(f) + sage: f.section() is f + True + """ + return self.__invert__() + + def is_surjective(self): + r""" + Return whether this morphism is surjective. + + EXAMPLES:: + + sage: f = sage.categories.morphism.SetIsomorphism(Hom(ZZ, ZZ, Sets()), + ....: operator.__neg__) + sage: f.is_surjective() + True + """ + return True + + def is_injective(self): + r""" + Return whether this morphism is injective. + + EXAMPLES:: + + sage: f = sage.categories.morphism.SetIsomorphism(Hom(ZZ, ZZ, Sets()), + ....: operator.__neg__) + sage: f.is_injective() + True + """ + return True From 6903923fb4efd63aac13bdb0de473c8fde90a16c Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 29 Feb 2024 22:11:04 -0800 Subject: [PATCH 2/2] FiniteRankFreeModule_abstract.isomorphism_with_fixed_basis: Return a SetIsomorphism --- .../tensor/modules/finite_rank_free_module.py | 109 +++++++++++++----- 1 file changed, 81 insertions(+), 28 deletions(-) diff --git a/src/sage/tensor/modules/finite_rank_free_module.py b/src/sage/tensor/modules/finite_rank_free_module.py index 0434ed84c53..cb0922eaa10 100644 --- a/src/sage/tensor/modules/finite_rank_free_module.py +++ b/src/sage/tensor/modules/finite_rank_free_module.py @@ -539,7 +539,9 @@ class :class:`~sage.modules.free_module.FreeModule_generic` from typing import Generator, Optional from sage.categories.fields import Fields +from sage.categories.homset import Hom from sage.categories.modules import Modules +from sage.categories.morphism import SetIsomorphism from sage.categories.rings import Rings from sage.misc.cachefunc import cached_method from sage.rings.integer import Integer @@ -755,15 +757,15 @@ def isomorphism_with_fixed_basis(self, basis=None, codomain=None): - ``codomain`` -- (default: ``None``) the codomain of the isomorphism represented by a free module within the category :class:`~sage.categories.modules_with_basis.ModulesWithBasis` with - the same rank and base ring as ``self``; if ``None`` a free module + the same rank and base ring as ``self``; if ``None``, a free module represented by :class:`~sage.combinat.free_module.CombinatorialFreeModule` is constructed OUTPUT: - - a module morphism represented by - :class:`~sage.modules.with_basis.morphism.ModuleMorphismFromFunction` + - a module isomorphism represented by + :class:`~sage.categories.morphism.SetIsomorphism` EXAMPLES:: @@ -800,6 +802,12 @@ def isomorphism_with_fixed_basis(self, basis=None, codomain=None): To: Free module generated by {'a', 'b', 'c'} over Rational Field sage: phi_eW(e[1] + 2 * e[2]) B['a'] + 2*B['b'] + sage: ~phi_eW + Generic morphism: + From: Free module generated by {'a', 'b', 'c'} over Rational Field + To: 3-dimensional vector space over the Rational Field + sage: (~phi_eW)(W.basis()['b']).display() + e_2 Providing a :class:`~sage.modules.free_module.Module_free_ambient` as the codomain:: @@ -814,7 +822,8 @@ def isomorphism_with_fixed_basis(self, basis=None, codomain=None): Sending (1,1)-tensors to matrices:: sage: T11 = V.tensor_module(1, 1); T11 - Free module of type-(1,1) tensors on the 3-dimensional vector space over the Rational Field + Free module of type-(1,1) tensors on the + 3-dimensional vector space over the Rational Field sage: e_T11 = T11.basis("e"); e_T11 Standard basis on the Free module of type-(1,1) tensors on the 3-dimensional vector space over the Rational Field @@ -822,7 +831,8 @@ def isomorphism_with_fixed_basis(self, basis=None, codomain=None): sage: W = MatrixSpace(QQ, 3) sage: phi_e_T11 = T11.isomorphism_with_fixed_basis(e_T11, codomain=W); phi_e_T11 Generic morphism: - From: Free module of type-(1,1) tensors on the 3-dimensional vector space over the Rational Field + From: Free module of type-(1,1) tensors on the + 3-dimensional vector space over the Rational Field To: Full MatrixSpace of 3 by 3 dense matrices over Rational Field sage: t = T11.an_element(); t.display() 1/2 e_1⊗e^1 @@ -830,20 +840,33 @@ def isomorphism_with_fixed_basis(self, basis=None, codomain=None): [1/2 0 0] [ 0 0 0] [ 0 0 0] + sage: ~phi_e_T11 + Generic morphism: + From: Full MatrixSpace of 3 by 3 dense matrices over Rational Field + To: Free module of type-(1,1) tensors on the + 3-dimensional vector space over the Rational Field + sage: (~phi_e_T11)(W([[0, 1/2, 1/3], + ....: [-1/2, 0, 0], + ....: [-1/3, 0, 0]])).display() + 1/2 e_1⊗e^2 + 1/3 e_1⊗e^3 - 1/2 e_2⊗e^1 - 1/3 e_3⊗e^1 Sending symmetric bilinear forms to matrices (note that they are currently elements of `T^{(0,2)}(M)`, not the symmetric power of `M`):: sage: T02 = V.tensor_module(0, 2); T02 - Free module of type-(0,2) tensors on the 3-dimensional vector space over the Rational Field + Free module of type-(0,2) tensors on the + 3-dimensional vector space over the Rational Field sage: e_T02 = T02.basis("e"); e_T02 Standard basis on the - Free module of type-(0,2) tensors on the 3-dimensional vector space over the Rational Field - induced by Basis (e_1,e_2,e_3) on the 3-dimensional vector space over the Rational Field + Free module of type-(0,2) tensors on the + 3-dimensional vector space over the Rational Field + induced by Basis (e_1,e_2,e_3) on the + 3-dimensional vector space over the Rational Field sage: W = MatrixSpace(QQ, 3) sage: phi_e_T02 = T02.isomorphism_with_fixed_basis(e_T02, codomain=W); phi_e_T02 Generic morphism: - From: Free module of type-(0,2) tensors on the 3-dimensional vector space over the Rational Field + From: Free module of type-(0,2) tensors on the + 3-dimensional vector space over the Rational Field To: Full MatrixSpace of 3 by 3 dense matrices over Rational Field sage: a = V.sym_bilinear_form() @@ -851,7 +874,8 @@ def isomorphism_with_fixed_basis(self, basis=None, codomain=None): sage: a[2,2], a[2,3] = 4, 5 sage: a[3,3] = 6 sage: a.display() - e^1⊗e^1 + 2 e^1⊗e^2 + 3 e^1⊗e^3 + 2 e^2⊗e^1 + 4 e^2⊗e^2 + 5 e^2⊗e^3 + 3 e^3⊗e^1 + 5 e^3⊗e^2 + 6 e^3⊗e^3 + e^1⊗e^1 + 2 e^1⊗e^2 + 3 e^1⊗e^3 + 2 e^2⊗e^1 + 4 e^2⊗e^2 + 5 e^2⊗e^3 + + 3 e^3⊗e^1 + 5 e^3⊗e^2 + 6 e^3⊗e^3 sage: phi_e_T02(a) [1 2 3] [2 4 5] @@ -860,15 +884,18 @@ def isomorphism_with_fixed_basis(self, basis=None, codomain=None): Same but explicitly in the subspace of symmetric bilinear forms:: sage: Sym2Vdual = V.dual_symmetric_power(2); Sym2Vdual - Free module of fully symmetric type-(0,2) tensors on the 3-dimensional vector space over the Rational Field + Free module of fully symmetric type-(0,2) tensors on the + 3-dimensional vector space over the Rational Field sage: Sym2Vdual.is_submodule(T02) True sage: Sym2Vdual.rank() 6 sage: e_Sym2Vdual = Sym2Vdual.basis("e"); e_Sym2Vdual Standard basis on the - Free module of fully symmetric type-(0,2) tensors on the 3-dimensional vector space over the Rational Field - induced by Basis (e_1,e_2,e_3) on the 3-dimensional vector space over the Rational Field + Free module of fully symmetric type-(0,2) tensors on the + 3-dimensional vector space over the Rational Field + induced by Basis (e_1,e_2,e_3) on the + 3-dimensional vector space over the Rational Field sage: W_basis = [phi_e_T02(b) for b in e_Sym2Vdual]; W_basis [ [1 0 0] [0 1 0] [0 0 1] [0 0 0] [0 0 0] [0 0 0] @@ -877,25 +904,34 @@ def isomorphism_with_fixed_basis(self, basis=None, codomain=None): ] sage: W = MatrixSpace(QQ, 3).submodule(W_basis); W Free module generated by {0, 1, 2, 3, 4, 5} over Rational Field - sage: phi_e_Sym2Vdual = Sym2Vdual.isomorphism_with_fixed_basis(e_Sym2Vdual, codomain=W); phi_e_Sym2Vdual + sage: phi_e_Sym2Vdual = Sym2Vdual.isomorphism_with_fixed_basis(e_Sym2Vdual, + ....: codomain=W) + sage: phi_e_Sym2Vdual Generic morphism: - From: Free module of fully symmetric type-(0,2) tensors on the 3-dimensional vector space over the Rational Field + From: Free module of fully symmetric type-(0,2) tensors on the + 3-dimensional vector space over the Rational Field To: Free module generated by {0, 1, 2, 3, 4, 5} over Rational Field Sending tensors to elements of the tensor square of :class:`CombinatorialFreeModule`:: sage: T20 = V.tensor_module(2, 0); T20 - Free module of type-(2,0) tensors on the 3-dimensional vector space over the Rational Field + Free module of type-(2,0) tensors on the + 3-dimensional vector space over the Rational Field sage: e_T20 = T02.basis("e"); e_T20 Standard basis on the - Free module of type-(0,2) tensors on the 3-dimensional vector space over the Rational Field - induced by Basis (e_1,e_2,e_3) on the 3-dimensional vector space over the Rational Field + Free module of type-(0,2) tensors on the + 3-dimensional vector space over the Rational Field + induced by Basis (e_1,e_2,e_3) on the + 3-dimensional vector space over the Rational Field sage: W = CombinatorialFreeModule(QQ, [1, 2, 3]).tensor_square(); W - Free module generated by {1, 2, 3} over Rational Field # Free module generated by {1, 2, 3} over Rational Field + Free module generated by {1, 2, 3} over Rational Field + # Free module generated by {1, 2, 3} over Rational Field sage: phi_e_T20 = T20.isomorphism_with_fixed_basis(e_T20, codomain=W); phi_e_T20 Generic morphism: - From: Free module of type-(2,0) tensors on the 3-dimensional vector space over the Rational Field - To: Free module generated by {1, 2, 3} over Rational Field # Free module generated by {1, 2, 3} over Rational Field + From: Free module of type-(2,0) tensors on the + 3-dimensional vector space over the Rational Field + To: Free module generated by {1, 2, 3} over Rational Field + # Free module generated by {1, 2, 3} over Rational Field sage: t = T20.an_element(); t.display() 1/2 e_1⊗e_1 sage: phi_e_T20(t) @@ -940,13 +976,17 @@ def isomorphism_with_fixed_basis(self, basis=None, codomain=None): codomain_basis = Family(codomain.basis()) if isinstance(codomain_basis, TrivialFamily): - # assume that codomain basis keys are to be ignored - key_pairs = enumerate(basis.keys()) + # assume that codomain basis keys are to be ignored; + # need them several times, can't keep as generators + key_pairs = tuple(enumerate(basis.keys())) + basis_by_codomain_key = basis else: # assume that the keys of the codomain should be used - key_pairs = zip(codomain_basis.keys(), basis.keys()) - # Need them several times, can't keep as generators - key_pairs = tuple(key_pairs) + # need them several times, can't keep as generators + key_pairs = tuple(zip(codomain_basis.keys(), basis.keys())) + basis_by_codomain_key = {} + for codomain_key, domain_key in key_pairs: + basis_by_codomain_key[codomain_key] = basis[domain_key] def _isomorphism(x): r""" @@ -955,7 +995,21 @@ def _isomorphism(x): return codomain.sum(x[basis, domain_key] * codomain_basis[codomain_key] for codomain_key, domain_key in key_pairs) - return self.module_morphism(function=_isomorphism, codomain=codomain) + def _inverse(y): + r""" + Concrete isomorphism from ``codomain`` to ``self``. + """ + return self.linear_combination( + (basis_by_codomain_key[codomain_key], coefficient) + for codomain_key, coefficient in y.monomial_coefficients().items()) + + category = Modules(self.base_ring()) + homset = Hom(self, codomain, category) + isomorphism = SetIsomorphism(homset, _isomorphism) + inverse = SetIsomorphism(homset.reversed(), _inverse) + isomorphism._set_inverse(inverse) + inverse._set_inverse(isomorphism) + return isomorphism def _test_isomorphism_with_fixed_basis(self, **options): r""" @@ -3160,7 +3214,6 @@ def hom(self, codomain, matrix_rep, bases=None, name=None, for more documentation. """ - from sage.categories.homset import Hom homset = Hom(self, codomain) return homset(matrix_rep, bases=bases, name=name, latex_name=latex_name)