Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions src/sage/categories/finite_dimensional_modules_with_basis.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@
import operator
from sage.categories.category_with_axiom import CategoryWithAxiom_over_base_ring
from sage.categories.fields import Fields
from sage.categories.homsets import HomsetsCategory
from sage.categories.tensor import TensorProductsCategory
from sage.misc.cachefunc import cached_method


class FiniteDimensionalModulesWithBasis(CategoryWithAxiom_over_base_ring):
"""
The category of finite dimensional modules with a distinguished basis
Expand Down Expand Up @@ -571,6 +573,88 @@ def _vector_(self, order=None):
self.dense_coefficient_list(order),
coerce=True, copy=False)

class Homsets(HomsetsCategory):

def extra_super_categories(self):
"""
EXAMPLES::

sage: Modules(ZZ).WithBasis().FiniteDimensional().Homsets().extra_super_categories()
[Category of finite dimensional modules with basis over Integer Ring]
"""
return [FiniteDimensionalModulesWithBasis(self.base_category().base_ring())]

class ParentMethods:
@cached_method
def basis(self):
r"""
Return the basis of ``self``.

EXAMPLES::

sage: C = CombinatorialFreeModule(ZZ, ['a', 'b', 'c'])
sage: R = CombinatorialFreeModule(ZZ, ['u', 'v'])
sage: HB = Hom(C, R).basis(); HB
Lazy family (basis_element(i))_{i in
The Cartesian product of ({'u', 'v'}, {'a', 'b', 'c'})}
sage: e_ub = HB[('u', 'b')]; e_ub
Generic morphism:
From: Free module generated by {'a', 'b', 'c'} over Integer Ring
To: Free module generated by {'u', 'v'} over Integer Ring
sage: e_ub(C.basis()['b'])
B['u']

sage: Hom(C, R).dimension()
6
"""
from sage.categories.cartesian_product import cartesian_product
from sage.sets.family import Family

domain = self.domain()
codomain = self.codomain()
domain_basis = domain.basis()
codomain_basis = codomain.basis()

def basis_element(key):
codomain_key, domain_key = key
return domain.module_morphism(on_basis=lambda k: codomain_basis[codomain_key] if k == domain_key else 0,
codomain=self.codomain())

return Family(cartesian_product([codomain_basis.keys(), domain_basis.keys()]),
basis_element)

class ElementMethods:
def monomial_coefficients(self, copy=True):
# This overrides the abstract method ModulesWithBasis.ElementMethods.monomial_coefficients;
# which is why we have to put it in ...Homsets.ElementMethods, not ...MorphismMethods.
r"""
Return a dictionary whose keys are indices of basis elements
in the support of ``self`` and whose values are the
corresponding coefficients.

INPUT:

- ``copy`` -- (default: ``True``) ignored

EXAMPLES::

sage: # needs sage.modules
sage: X = CombinatorialFreeModule(ZZ, [1,2]); x = X.basis()
sage: Y = CombinatorialFreeModule(ZZ, [3,4]); y = Y.basis()
sage: phi = X.module_morphism(on_basis={1: y[3] + 3*y[4],
....: 2: 2*y[3] + 5*y[4]}.__getitem__,
....: codomain=Y)
sage: phi.category_for()
Category of finite dimensional modules with basis over Integer Ring
sage: phi.monomial_coefficients()
{(3, 1): 1, (3, 2): 2, (4, 1): 3, (4, 2): 5}
"""
on_basis = self.on_basis()
domain_keys = self.domain().basis().keys()
return {(codomain_key, domain_key): coefficient
for domain_key in self.domain().basis().keys()
for codomain_key, coefficient in on_basis(domain_key).monomial_coefficients().items()}

class MorphismMethods:
def matrix(self, base_ring=None, side="left"):
r"""
Expand Down
10 changes: 6 additions & 4 deletions src/sage/categories/modules_with_basis.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,19 +105,20 @@ class ModulesWithBasis(CategoryWithAxiom_over_base_ring):

`Hom(X,Y)` has a natural module structure (except for the zero,
the operations are not yet implemented though). However since the
dimension is not necessarily finite, it is not a module with
dimension is not necessarily finite, in general it is not a module with
basis; but see :class:`FiniteDimensionalModulesWithBasis` and
:class:`GradedModulesWithBasis`::

sage: H in ModulesWithBasis(QQ), H in Modules(QQ) # needs sage.modules
(False, True)
(True, True)

Some more playing around with categories and higher order homsets::

sage: H.category() # needs sage.modules
Category of homsets of modules with basis over Rational Field
Category of homsets of finite dimensional modules with basis over Rational Field
sage: Hom(H, H).category() # needs sage.modules
Category of endsets of homsets of modules with basis over Rational Field
Category of endsets of
homsets of finite dimensional modules with basis over Rational Field

.. TODO:: ``End(X)`` is an algebra.

Expand Down Expand Up @@ -2229,6 +2230,7 @@ def tensor(*elements):
return tensor(parents)._tensor_of_elements(elements) # good name ?

class Homsets(HomsetsCategory):

class ParentMethods:
def __call_on_basis__(self, **options):
"""
Expand Down
63 changes: 33 additions & 30 deletions src/sage/modules/matrix_morphism.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,11 @@ def is_MatrixMorphism(x):
class MatrixMorphism_abstract(sage.categories.morphism.Morphism):
def __init__(self, parent, side='left'):
"""
INPUT:

- ``parent`` - a homspace
Initialize ``self``.

- ``A`` - matrix
INPUT:

- ``parent`` -- a homspace

EXAMPLES::

Expand Down Expand Up @@ -117,8 +116,8 @@ def _richcmp_(self, other, op):
True
sage: psi == 5 * id
True
sage: psi == 5 # no coercion
False
sage: psi == 5
True
sage: id == End(V).identity()
True
"""
Expand Down Expand Up @@ -458,10 +457,27 @@ def __rmul__(self, left):
Free module morphism defined by the matrix
[2 2]
[0 4]...

TESTS:

Check that :trac:`28272` is fixed::

sage: V = VectorSpace(QQ,2)
sage: f = V.hom(identity_matrix(QQ,2))
sage: 1/2 * f
Vector space morphism represented by the matrix:
[1/2 0]
[ 0 1/2]...
sage: f * 1/2
Vector space morphism represented by the matrix:
[1/2 0]
[ 0 1/2]...
"""
R = self.base_ring()
return self.parent()(R(left) * self.matrix(), side=self.side())

_rmul_ = _lmul_ = __rmul__

def __mul__(self, right):
r"""
Composition of morphisms, denoted by \*.
Expand Down Expand Up @@ -634,7 +650,7 @@ def __mul__(self, right):
else:
return H(right.matrix() * self.matrix().transpose(), side="left")

def __add__(self, right):
def _add_(self, other):
"""
Sum of morphisms, denoted by +.

Expand Down Expand Up @@ -668,11 +684,10 @@ def __add__(self, right):
sage: phi + psi
Traceback (most recent call last):
...
ValueError: inconsistent number of rows: should be 2 but got 3
TypeError: unsupported operand parent(s) for +: ...

::


sage: V = ZZ^2
sage: m = matrix(2, [1,1,0,1])
sage: hl = V.hom(m)
Expand All @@ -699,22 +714,16 @@ def __add__(self, right):
If the two morphisms do not share the same ``side`` attribute, then
the resulting morphism will be defined with the default value.
"""
# TODO: move over to any coercion model!
if not isinstance(right, MatrixMorphism):
R = self.base_ring()
return self.parent()(self.matrix() + R(right))
if not right.parent() == self.parent():
right = self.parent()(right, side=right.side())
if self.side() == "left":
if right.side() == "left":
return self.parent()(self.matrix() + right.matrix(), side=self.side())
elif right.side() == "right":
return self.parent()(self.matrix() + right.matrix().transpose(), side="left")
if other.side() == "left":
return self.parent()(self.matrix() + other.matrix(), side=self.side())
elif other.side() == "right":
return self.parent()(self.matrix() + other.matrix().transpose(), side="left")
if self.side() == "right":
if right.side() == "right":
return self.parent()(self.matrix() + right.matrix(), side=self.side())
elif right.side() == "left":
return self.parent()(self.matrix().transpose() + right.matrix(), side="left")
if other.side() == "right":
return self.parent()(self.matrix() + other.matrix(), side=self.side())
elif other.side() == "left":
return self.parent()(self.matrix().transpose() + other.matrix(), side="left")

def __neg__(self):
"""
Expand All @@ -732,7 +741,7 @@ def __neg__(self):
"""
return self.parent()(-self.matrix(), side=self.side())

def __sub__(self, other):
def _sub_(self, other):
"""
EXAMPLES::

Expand Down Expand Up @@ -770,12 +779,6 @@ def __sub__(self, other):
If the two morphisms do not share the same ``side`` attribute, then
the resulting morphism will be defined with the default value.
"""
# TODO: move over to any coercion model!
if not isinstance(other, MatrixMorphism):
R = self.base_ring()
return self.parent()(self.matrix() - R(other), side=self.side())
if not other.parent() == self.parent():
other = self.parent()(other, side=other.side())
if self.side() == "left":
if other.side() == "left":
return self.parent()(self.matrix() - other.matrix(), side=self.side())
Expand Down