Skip to content

Commit

Permalink
Trac #30235: Add construction methods to FiniteRankFreeModule, Combin…
Browse files Browse the repository at this point in the history
…atorialFreeModule and Cartesian products

(follow-up from #30194)

... extending `sage.categories.pushout.VectorFunctor`

for example:
{{{
            sage: M = FreeModule(ZZ, 4, with_basis=None, name='M')
            sage: latex(M)
            M
            sage: from sage.categories.pushout import VectorFunctor,
pushout
            sage: M_QQ = pushout(M, QQ)
            sage: latex(M_QQ)
            M \otimes \Bold{Q}
}}}

URL: https://trac.sagemath.org/30235
Reported by: mkoeppe
Ticket author(s): Matthias Koeppe
Reviewer(s): Travis Scrimshaw
  • Loading branch information
Release Manager committed Aug 28, 2022
2 parents a444241 + 3f44174 commit 3f624d5
Show file tree
Hide file tree
Showing 9 changed files with 271 additions and 14 deletions.
115 changes: 103 additions & 12 deletions src/sage/categories/pushout.py
Original file line number Diff line number Diff line change
Expand Up @@ -1822,14 +1822,17 @@ 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:
- ``n``, the rank of the to-be-created modules (non-negative integer)
- ``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::
Expand Down Expand Up @@ -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()
Expand All @@ -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):
"""
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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):
Expand Down
13 changes: 13 additions & 0 deletions src/sage/categories/sets_cat.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
"""
Expand Down
28 changes: 26 additions & 2 deletions src/sage/combinat/free_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand Down
13 changes: 13 additions & 0 deletions src/sage/manifolds/differentiable/tangent_space.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.<x,y> = 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``.
Expand Down
13 changes: 13 additions & 0 deletions src/sage/manifolds/vector_bundle_fiber.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.<x,y,z> = 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``.
Expand Down
28 changes: 28 additions & 0 deletions src/sage/modules/free_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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:
Expand Down
30 changes: 30 additions & 0 deletions src/sage/tensor/modules/ext_pow_free_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
Loading

0 comments on commit 3f624d5

Please sign in to comment.