Skip to content

Commit

Permalink
Trac #33321: Implement GradedModulesWithBasis(R).Subobjects() and Fil…
Browse files Browse the repository at this point in the history
…teredModulesWithBasis(R).Subobjects()

Currently, graded submodules do not inherit grading from its ambient:
{{{
sage: M = ModulesWithBasis(QQ).Graded().example()
sage: M(Partition((4,2,1,1,1,1))).degree()
10
sage: N = M.submodule([M(Partition((4,2,1,1,1,1)))],
category=GradedModulesWithBasis(QQ).Subobjects())
sage: n = N.basis()
sage: n[0].degree()
------------------------------------------------------------------------
---
AttributeError                            Traceback (most recent call
last)
...
AttributeError: 'SubmoduleWithBasis_with_category' object has no
attribute 'degree_on_basis'
}}}
We implement this by fulfilling the regressive category construction
requirement for the `GradedModules().Subobjects()` category. We also do
this for filtered modules (every submodule of a filtered module has a
natural filtration inherited from the ambient module).

Want: In the case where the generators are not from homogeneous but can
made homogeneous by row reducing, it inherits its degrees. If the basis
elements cannot be made homogeneous by row reduction, raise an error
when trying to use `graded_submodule`. For example,
{{{
sage: M = ModulesWithBasis(QQ).Graded().example()
sage: N = M.graded_submodule([M(Partition((4,2,1,1,1,1))) -
M(Partition((5,3))),
      M(Partition((5,3)))])
sage: n = N.basis()
sage: (n[0].lift(), n[1].lift())
(P[4, 2, 1, 1, 1, 1], P[5,3])
sage: (n[0].degree(), n[1].degree())
(10, 8)
sage: K = M.graded_submodule([M(Partition((4,2,1,1,1,1))) -
M(Partition((5,3))),
      M(Partition((5,3)) - M(Partition((4,3)))])
Traceback (most recent call last):
...
ValueError: element is not homogeneous
}}}

URL: https://trac.sagemath.org/33321
Reported by: gh-louisng114
Ticket author(s): Louis Ng, Travis Scrimshaw
Reviewer(s): Matthias Koeppe
  • Loading branch information
Release Manager committed Mar 9, 2022
2 parents f4e532f + e5901b5 commit ca4f346
Show file tree
Hide file tree
Showing 8 changed files with 341 additions and 12 deletions.
82 changes: 82 additions & 0 deletions src/sage/categories/filtered_modules_with_basis.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from sage.categories.filtered_modules import FilteredModulesCategory
from sage.misc.abstract_method import abstract_method
from sage.misc.cachefunc import cached_method
from sage.categories.subobjects import SubobjectsCategory

class FilteredModulesWithBasis(FilteredModulesCategory):
r"""
Expand Down Expand Up @@ -987,4 +988,85 @@ def truncate(self, n):
return self.parent().sum_of_terms((i, c) for (i, c) in self
if degree_on_basis(i) < n)

class Subobjects(SubobjectsCategory):
class ParentMethods:
def degree_on_basis(self, m):
r"""
Return the degree of the basis element indexed by ``m``
in ``self``.
EXAMPLES::
sage: E.<x,y,z> = ExteriorAlgebra(QQ)
sage: S = E.submodule([x + y, x*y - y*z, y])
sage: B = S.basis()
sage: [B[0].lift(), B[1].lift(), B[2].lift()]
[x, y, x*y - y*z]
sage: S.degree_on_basis(0)
1
sage: S.degree_on_basis(1)
1
sage: S.degree_on_basis(2)
2
"""
return self.basis()[m].lift().degree()

class ElementMethods:
def degree(self):
r"""
Return the degree of ``self``.
EXAMPLES::
sage: E.<x,y,z> = ExteriorAlgebra(QQ)
sage: S = E.submodule([x + y, x*y - y*z, y])
sage: B = S.basis()
sage: [B[0].lift(), B[1].lift(), B[2].lift()]
[x, y, x*y - y*z]
sage: B[0].degree()
1
sage: B[1].degree()
1
sage: (B[0] + 3*B[1]).degree()
1
The degree of inhomogeneous elements is not defined
(following the behavior of the exterior algebra)::
sage: (B[0] + B[2]).degree()
Traceback (most recent call last):
...
ValueError: element is not homogeneous
We can still get the maximal degree::
sage: (B[0] + B[2]).maximal_degree()
2
"""
return self.lift().degree()

def maximal_degree(self):
r"""
The maximum of the degrees of the homogeneous components
of ``self``.
This is also the smallest `i` such that ``self`` belongs
to `F_i`. Hence, it does not depend on the basis of the
parent of ``self``.
.. SEEALSO:: :meth:`homogeneous_degree`
EXAMPLES::
sage: E.<x,y,z> = ExteriorAlgebra(QQ)
sage: F = E.submodule([x + 1, x*y - 1])
sage: B = F.basis()
sage: [B[0].lift(), B[1].lift()]
[-x*y + 1, x*y + x]
sage: B[0].maximal_degree()
2
sage: B[1].maximal_degree()
2
"""
return self.lift().maximal_degree()

10 changes: 10 additions & 0 deletions src/sage/categories/finite_dimensional_modules_with_basis.py
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,17 @@ def echelon_form(self, elements, row_reduced=False, order=None):
[0 0 0]
[0 0 0]
]
TESTS:
We convert the input elements to ``self``::
sage: E.<x,y,z> = ExteriorAlgebra(QQ)
sage: E.echelon_form([1, x + 2])
[1, x]
"""
# Make sure elements consists of elements of ``self``
elements = [self(y) for y in elements]
if order is not None:
order = self._compute_support_order(elements, order)
from sage.matrix.constructor import matrix
Expand Down
219 changes: 218 additions & 1 deletion src/sage/categories/graded_modules_with_basis.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#******************************************************************************

from sage.categories.graded_modules import GradedModulesCategory

from sage.categories.quotients import QuotientsCategory

class GradedModulesWithBasis(GradedModulesCategory):
"""
Expand Down Expand Up @@ -67,6 +67,185 @@ def degree_negation(self, element):
for key, value in
element.monomial_coefficients(copy=False).items()])


def submodule(self, gens, check=True, already_echelonized=False,
unitriangular=False, support_order=None, category=None,
*args, **opts):
r"""
Return the submodule spanned by a finite set of elements.
INPUT:
- ``gens`` -- a list or family of elements of ``self``
- ``check`` -- (default: ``True``) whether to verify that the
elements of ``gens`` are in ``self``
- ``already_echelonized`` -- (default: ``False``) whether
the elements of ``gens`` are already in (not necessarily
reduced) echelon form
- ``unitriangular`` -- (default: ``False``) whether
the lift morphism is unitriangular
- ``support_order`` -- (optional) either something that can
be converted into a tuple or a key function
- ``category`` -- (optional) the category of the submodule
If ``already_echelonized`` is ``False``, then the
generators are put in reduced echelon form using
:meth:`echelonize`, and reindexed by `0,1,...`.
.. WARNING::
At this point, this method only works for finite
dimensional submodules and if matrices can be
echelonized over the base ring.
If in addition ``unitriangular`` is ``True``, then
the generators are made such that the coefficients of
the pivots are 1, so that lifting map is unitriangular.
The basis of the submodule uses the same index set as the
generators, and the lifting map sends `y_i` to `gens[i]`.
.. SEEALSO::
- :meth:`ModulesWithBasis.FiniteDimensional.ParentMethods.quotient_module`
- :class:`sage.modules.with_basis.subquotient.SubmoduleWithBasis`
EXAMPLES:
A graded submodule of a graded module generated by homogeneous
elements is naturally graded::
sage: E.<x,y,z> = ExteriorAlgebra(QQ)
sage: S = E.submodule([x + y, x*y - y*z])
sage: S.category()
Join of Category of graded vector spaces with basis over Rational Field
and Category of subobjects of filtered modules with basis over Rational Field
and Category of finite dimensional vector spaces with basis over Rational Field
sage: S.basis()[0].degree()
1
sage: S.basis()[1].degree()
2
We check on the echelonized basis::
sage: Sp = E.submodule([1, x + y + 5, x*y - y*z + x + y - 2])
sage: Sp.category()
Join of Category of graded vector spaces with basis over Rational Field
and Category of subobjects of filtered modules with basis over Rational Field
and Category of finite dimensional vector spaces with basis over Rational Field
If it is generated by inhomogeneous elements, then it is
filtered by default::
sage: F = E.submodule([x + y*z, x*z + y*x])
sage: F.category()
Join of Category of subobjects of filtered modules with basis over Rational Field
and Category of filtered vector spaces with basis over Rational Field
and Category of finite dimensional vector spaces with basis over Rational Field
If ``category`` is specified, then it does not give any extra
structure to the submodule (we can think of this as applying
the forgetful functor)::
sage: SM = E.submodule([x + y, x*y - y*z], category=ModulesWithBasis(QQ))
sage: SM.category()
Join of Category of finite dimensional vector spaces with basis over Rational Field
and Category of subobjects of sets
sage: FM = E.submodule([x + 1, x*y - x*y*z], category=ModulesWithBasis(QQ))
sage: FM.category()
Join of Category of finite dimensional vector spaces with basis over Rational Field
and Category of subobjects of sets
If we have specified that this is a graded submodule of a graded
module, then the echelonized elements must be homogeneous::
sage: Cat = ModulesWithBasis(QQ).Graded().Subobjects()
sage: E.submodule([x + y, x*y - 1], category=Cat)
Traceback (most recent call last):
...
ValueError: all of the generators must be homogeneous
sage: E.submodule([x + y, x*y - x - y], category=Cat)
Free module generated by {0, 1} over Rational Field
"""
# Make sure gens consists of elements of ``self``
from sage.sets.family import Family, AbstractFamily
if isinstance(gens, AbstractFamily):
gens = gens.map(self)
elif isinstance(gens, dict):
gens = Family(gens.keys(), gens.__getitem__)
else:
gens = [self(y) for y in gens]
support_order = self._compute_support_order(gens, support_order)
if not already_echelonized:
gens = self.echelon_form(gens, unitriangular, order=support_order)

GMod = GradedModulesWithBasis(self.category().base_ring())
if category is None:
if all(g.is_homogeneous() for g in gens):
category = GMod.Subobjects()
elif (category.is_subcategory(GMod.Subobjects())
and not all(g.is_homogeneous() for g in gens)):
raise ValueError("all of the generators must be homogeneous")

from sage.modules.with_basis.subquotient import SubmoduleWithBasis
return SubmoduleWithBasis(gens, ambient=self,
support_order=support_order,
unitriangular=unitriangular,
category=category, *args, **opts)

def quotient_module(self, submodule, check=True, already_echelonized=False, category=None):
r"""
Construct the quotient module ``self`` / ``submodule``.
INPUT:
- ``submodule`` -- a submodule with basis of ``self``, or
something that can be turned into one via
``self.submodule(submodule)``
- ``check``, ``already_echelonized`` -- passed down to
:meth:`ModulesWithBasis.ParentMethods.submodule`
- ``category`` -- (optional) the category of the quotient module
.. WARNING::
At this point, this only supports quotients by free
submodules admitting a basis in unitriangular echelon
form. In this case, the quotient is also a free
module, with a basis consisting of the retract of a
subset of the basis of ``self``.
EXAMPLES::
sage: E.<x,y,z> = ExteriorAlgebra(QQ)
sage: S = E.submodule([x + y, x*y - y*z, y])
sage: Q = E.quotient_module(S)
sage: Q.category()
Join of Category of quotients of graded modules with basis over Rational Field
and Category of graded vector spaces with basis over Rational Field
and Category of finite dimensional vector spaces with basis over Rational Field
.. SEEALSO::
- :meth:`Modules.WithBasis.ParentMethods.submodule`
- :meth:`Rings.ParentMethods.quotient`
- :class:`sage.modules.with_basis.subquotient.QuotientModuleWithBasis`
"""
from sage.modules.with_basis.subquotient import SubmoduleWithBasis, QuotientModuleWithBasis
if not isinstance(submodule, SubmoduleWithBasis):
submodule = self.submodule(submodule, check=check,
unitriangular=True,
already_echelonized=already_echelonized)

GMod = GradedModulesWithBasis(self.category().base_ring())
if category is None:
if all(g.is_homogeneous() for g in submodule.basis()):
category = GMod.Quotients()
elif (category.is_subcategory(GMod.Quotients())
and not all(g.is_homogeneous() for g in submodule.basis())):
raise ValueError("all of the basis elements must be homogeneous")

return QuotientModuleWithBasis(submodule, category=category)

class ElementMethods:
def degree_negation(self):
r"""
Expand Down Expand Up @@ -95,3 +274,41 @@ def degree_negation(self):
"""
return self.parent().degree_negation(self)

class Quotients(QuotientsCategory):
class ParentMethods:
def degree_on_basis(self, m):
r"""
Return the degree of the basis element indexed by ``m``
in ``self``.
EXAMPLES::
sage: E.<x,y,z> = ExteriorAlgebra(QQ)
sage: S = E.submodule([x + y, x*y - y*z, y])
sage: Q = E.quotient_module(S)
sage: B = Q.basis()
sage: [B[i].lift() for i in Q.indices()]
[1, z, x*z, y*z, x*y*z]
sage: [Q.degree_on_basis(i) for i in Q.indices()]
[0, 1, 2, 2, 3]
"""
return self.basis()[m].lift().degree()

class ElementMethods:
def degree(self):
r"""
Return the degree of ``self``.
EXAMPLES::
sage: E.<x,y,z> = ExteriorAlgebra(QQ)
sage: S = E.submodule([x + y, x*y - y*z, y])
sage: Q = E.quotient_module(S)
sage: B = Q.basis()
sage: [B[i].lift() for i in Q.indices()]
[1, z, x*z, y*z, x*y*z]
sage: [B[i].degree() for i in Q.indices()]
[0, 1, 2, 2, 3]
"""
return self.lift().degree()

Loading

0 comments on commit ca4f346

Please sign in to comment.