Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding support for sums, intersection, and equality of SubmodulesWithBasis #36988

Merged
Merged
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
31 changes: 31 additions & 0 deletions src/sage/categories/modules_with_basis.py
Original file line number Diff line number Diff line change
Expand Up @@ -986,6 +986,37 @@ def tensor(*parents, **kwargs):
cat = constructor.category_from_parents(parents)
return parents[0].__class__.Tensor(parents, category=cat)

def intersection(self, other):
r"""
Return the intersection of ``self`` with ``other``.

EXAMPLES::

sage: X = CombinatorialFreeModule(QQ, range(4)); x = X.basis()
sage: U = X.submodule([x[0]-x[1], x[1]-x[2], x[2]-x[3]])
sage: F = CombinatorialFreeModule(QQ, ['a','b','c','d'])
sage: G = F.submodule([F.basis()['a']])
sage: X.intersection(X) is X
True
sage: X.intersection(U) is U
True
sage: X.intersection(F)
Traceback (most recent call last):
...
TypeError: other must be a submodule
sage: X.intersection(G)
Traceback (most recent call last):
...
ArithmeticError: this module must be the ambient
"""
if other is self:
return self
if other not in self.category().Subobjects():
raise TypeError("other must be a submodule")
if other.ambient() != self:
raise ArithmeticError("this module must be the ambient")
return other

def cardinality(self):
"""
Return the cardinality of ``self``.
Expand Down
310 changes: 302 additions & 8 deletions src/sage/modules/with_basis/subquotient.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@
x[2]
"""
assert x in self
return self.ambient()._from_dict(x._monomial_coefficients)
return self._ambient._from_dict(x._monomial_coefficients)

def retract(self, x):
r"""
Expand Down Expand Up @@ -194,7 +194,6 @@
- :meth:`Modules.WithBasis.ParentMethods.submodule`
- :class:`QuotientModuleWithBasis`
"""

@staticmethod
def __classcall_private__(cls, basis, support_order, ambient=None,
unitriangular=False, category=None, *args, **opts):
Expand Down Expand Up @@ -300,7 +299,7 @@
x[0] - x[2]
"""
return self.module_morphism(self.lift_on_basis,
codomain=self.ambient(),
codomain=self._ambient,
triangular="lower",
unitriangular=self._unitriangular,
key=self._support_key,
Expand Down Expand Up @@ -357,12 +356,13 @@
return self.lift.section()

def is_submodule(self, other):
"""
r"""
Return whether ``self`` is a submodule of ``other``.

INPUT:

- ``other`` -- another submodule of the same ambient module, or the ambient module itself
- ``other`` -- another submodule of the same ambient module
or the ambient module itself

EXAMPLES::

Expand All @@ -376,16 +376,310 @@
True
sage: H.is_submodule(F)
False
sage: H.is_submodule(G)
False

Infinite dimensional examples::

sage: X = CombinatorialFreeModule(QQ, ZZ); x = X.basis()
sage: F = X.submodule([x[0]-x[1], x[1]-x[2], x[2]-x[3]])
sage: G = X.submodule([x[0]-x[2]])
sage: H = X.submodule([x[0]-x[1]])
sage: F.is_submodule(X)
True
sage: G.is_submodule(F)
True
sage: H.is_submodule(F)
True
sage: H.is_submodule(G)
False

TESTS::

sage: X = CombinatorialFreeModule(QQ, range(4)); x = X.basis()
sage: F = X.submodule([x[0]-x[1], x[1]-x[2], x[2]-x[3]])
sage: Y = CombinatorialFreeModule(QQ, range(6)); y = Y.basis()
sage: G = Y.submodule([y[0]-y[1], y[1]-y[2], y[2]-y[3]])
sage: F.is_submodule(G)
Traceback (most recent call last):
...
ValueError: other (=...) should be a submodule of the same ambient space
"""
if other is self.ambient():
if other is self._ambient:
return True
if not isinstance(self, SubmoduleWithBasis) and self.ambient() is other.ambient():
if not (isinstance(self, SubmoduleWithBasis) and self.ambient() is other.ambient()):
raise ValueError("other (=%s) should be a submodule of the same ambient space" % other)
if self not in ModulesWithBasis.FiniteDimensional:
raise NotImplementedError("is_submodule for infinite dimensional modules")
raise NotImplementedError("only implemented for finite dimensional submodules")

Check warning on line 413 in src/sage/modules/with_basis/subquotient.py

View check run for this annotation

Codecov / codecov/patch

src/sage/modules/with_basis/subquotient.py#L413

Added line #L413 was not covered by tests
if self.dimension() > other.dimension(): # quick dimension check
return False
if not set(self._support_order) <= set(other._support_order): # quick support check
return False
for b in self.basis():
try:
other.retract(b.lift())
except ValueError:
return False
return True

def _common_submodules(self, other):
"""
Helper method to return a pair of submodules of the same ambient
free modules to do the corresponding linear algebra.

EXAMPLES::

sage: X = CombinatorialFreeModule(QQ, range(4)); x = X.basis()
sage: F = X.submodule([x[0]-x[1], x[1]-3*x[2], x[2]-5*x[3]])
sage: G = X.submodule([x[0]-x[1], x[1]-2*x[2], x[2]-3*x[3]])
sage: H = X.submodule([x[0]-x[1], x[1]-2*x[2], x[2]-3*x[3]], support_order=(3,2,1,0))
sage: F._common_submodules(G)
(Vector space of degree 4 and dimension 3 over Rational Field
Basis matrix:
[ 1 0 0 -15]
[ 0 1 0 -15]
[ 0 0 1 -5],
Vector space of degree 4 and dimension 3 over Rational Field
Basis matrix:
[ 1 0 0 -6]
[ 0 1 0 -6]
[ 0 0 1 -3])
sage: H._common_submodules(F)
(Vector space of degree 4 and dimension 3 over Rational Field
Basis matrix:
[ 1 0 0 -1/6]
[ 0 1 0 -1/2]
[ 0 0 1 -1],
Vector space of degree 4 and dimension 3 over Rational Field
Basis matrix:
[ 1 0 0 -1/15]
[ 0 1 0 -1/3]
[ 0 0 1 -1])
sage: G._common_submodules(H)
(Vector space of degree 4 and dimension 3 over Rational Field
Basis matrix:
[ 1 0 0 -6]
[ 0 1 0 -6]
[ 0 0 1 -3],
Vector space of degree 4 and dimension 3 over Rational Field
Basis matrix:
[ 1 0 0 -6]
[ 0 1 0 -6]
[ 0 0 1 -3])
sage: H._common_submodules(G)
(Vector space of degree 4 and dimension 3 over Rational Field
Basis matrix:
[ 1 0 0 -1/6]
[ 0 1 0 -1/2]
[ 0 0 1 -1],
Vector space of degree 4 and dimension 3 over Rational Field
Basis matrix:
[ 1 0 0 -1/6]
[ 0 1 0 -1/2]
[ 0 0 1 -1])
"""
from sage.modules.free_module import FreeModule
supp_order = self._support_order
A = FreeModule(self.base_ring(), len(supp_order))
U = A.submodule([A([vec[supp] for supp in supp_order]) for vec in self._basis], check=False)
V = A.submodule([A([vec[supp] for supp in supp_order]) for vec in other._basis], check=False)
return (U, V)

def is_equal_subspace(self, other):
r"""
Return whether ``self`` is an equal submodule to ``other``.

.. NOTE::

This is the mathematical notion of equality (as sets that are
isomorphic as vector spaces), which is weaker than the `==`
which takes into account things like the support order.

INPUT:

- ``other`` -- another submodule of the same ambient module
or the ambient module itself

EXAMPLES::

sage: R.<z> = LaurentPolynomialRing(QQ)
sage: X = CombinatorialFreeModule(R, range(4)); x = X.basis()
sage: F = X.submodule([x[0]-x[1], z*x[1]-z*x[2], z^2*x[2]-z^2*x[3]])
sage: G = X.submodule([x[0]-x[1], x[1]-x[2], x[2]-x[3]])
sage: H = X.submodule([x[0]-x[1], x[1]-x[2], x[2]-x[3]], support_order=(3,2,1,0))
sage: F.is_equal_subspace(F)
True
sage: F == G
False
sage: F.is_equal_subspace(G)
True
sage: F.is_equal_subspace(H)
True
sage: G == H # different support orders
False
sage: G.is_equal_subspace(H)
True

::

sage: X = CombinatorialFreeModule(QQ, ZZ); x = X.basis()
sage: F = X.submodule([x[0]-x[1], x[1]-x[3]])
sage: G = X.submodule([x[0]-x[1], x[2]])
sage: H = X.submodule([x[0]+x[1], x[1]+3*x[2]])
sage: Hp = X.submodule([x[0]+x[1], x[1]+3*x[2]], prefix='Hp')
sage: F.is_equal_subspace(X)
False
sage: F.is_equal_subspace(G)
False
sage: G.is_equal_subspace(H)
False
sage: H == Hp
False
sage: H.is_equal_subspace(Hp)
True
"""
if self is other: # trivial case
return True
if not isinstance(self, SubmoduleWithBasis) and self.ambient() is other.ambient():
raise ArithmeticError("other (=%s) should be a submodule of the same ambient space" % other)

Check warning on line 544 in src/sage/modules/with_basis/subquotient.py

View check run for this annotation

Codecov / codecov/patch

src/sage/modules/with_basis/subquotient.py#L544

Added line #L544 was not covered by tests
if self.dimension() != other.dimension(): # quick dimension check
return False
if self not in ModulesWithBasis.FiniteDimensional:
raise NotImplementedError("only implemented for finite dimensional submodules")

Check warning on line 548 in src/sage/modules/with_basis/subquotient.py

View check run for this annotation

Codecov / codecov/patch

src/sage/modules/with_basis/subquotient.py#L548

Added line #L548 was not covered by tests
if set(self._basis) == set(other._basis):
return True
if set(self._support_order) != set(other._support_order): # different supports
return False
U, V = self._common_submodules(other)
return U == V

def __add__(self, other):
r"""
Return the sum of ``self`` and ``other``.

EXAMPLES::

sage: X = CombinatorialFreeModule(QQ, range(4)); x = X.basis()
sage: F = X.submodule([x[0]-x[1], x[1]-x[2], x[2]-x[3]])
sage: G = X.submodule([x[0]-x[2]])
sage: H = X.submodule([x[0]-x[1], x[2]])
sage: FG = F + G; FG
Free module generated by {0, 1, 2} over Rational Field
sage: [FG.lift(b) for b in FG.basis()]
[B[0] - B[3], B[1] - B[3], B[2] - B[3]]
sage: FH = F + H; FH
Free module generated by {0, 1, 2, 3} over Rational Field
sage: [FH.lift(b) for b in FH.basis()]
[B[0], B[1], B[2], B[3]]
sage: GH = G + H; GH
Free module generated by {0, 1, 2} over Rational Field
sage: [GH.lift(b) for b in GH.basis()]
[B[0], B[1], B[2]]

TESTS::

sage: X = CombinatorialFreeModule(QQ, range(4)); x = X.basis()
sage: F = X.submodule([x[0]-x[1], x[1]-x[2], x[2]-x[3]])
sage: Y = CombinatorialFreeModule(QQ, range(5)); y = Y.basis()
sage: U = Y.submodule([y[0]-y[2]+y[3]])
sage: F + U
Traceback (most recent call last):
...
ArithmeticError: both subspaces must have the same ambient space
sage: F + 3
Traceback (most recent call last):
...
TypeError: both objects must be submodules
"""
if not isinstance(other, SubmoduleWithBasis):
raise TypeError("both objects must be submodules")
if other.ambient() != self.ambient():
raise ArithmeticError("both subspaces must have the same ambient space")
return self.ambient().submodule(set(list(self._basis) + list(other._basis)), check=False)

subspace_sum = __add__

def __and__(self, other):
r"""
Return the intersection of ``self`` and ``other``.

EXAMPLES::

sage: X = CombinatorialFreeModule(QQ, range(4)); x = X.basis()
sage: F = X.submodule([x[0]-x[1], x[1]-x[2], x[2]-x[3]])
sage: G = X.submodule([x[0]-x[2]])
sage: H = X.submodule([x[0]-x[1], x[2]])
sage: FG = F & G; FG
Free module generated by {0} over Rational Field
sage: [FG.lift(b) for b in FG.basis()]
[B[0] - B[2]]
sage: FH = F & H; FH
Free module generated by {0} over Rational Field
sage: [FH.lift(b) for b in FH.basis()]
[B[0] - B[1]]
sage: GH = G & H; GH
Free module generated by {} over Rational Field
sage: [GH.lift(b) for b in GH.basis()]
[]

sage: F.intersection(X) is F
True

TESTS::

sage: X = CombinatorialFreeModule(QQ, range(4)); x = X.basis()
sage: F = X.submodule([x[0]-x[1], x[1]-x[2], x[2]-x[3]])
sage: Y = CombinatorialFreeModule(QQ, range(5)); y = Y.basis()
sage: U = Y.submodule([y[0]-y[2]+y[3]])
sage: F & U
Traceback (most recent call last):
...
ArithmeticError: both subspaces must have the same ambient space
sage: F & 3
Traceback (most recent call last):
...
TypeError: both objects must be submodules
"""
if other is self._ambient:
return self
if not isinstance(other, SubmoduleWithBasis):
raise TypeError("both objects must be submodules")
if other.ambient() != self.ambient():
raise ArithmeticError("both subspaces must have the same ambient space")
U, V = self._common_submodules(other)
UV = U & V # the intersection
A = self._ambient
supp = self._support_order
return A.submodule([A.element_class(A, {supp[i]: c for i, c in vec.iteritems()})
for vec in UV.basis()])

intersection = __and__
__rand__ = __and__

def subspace(self, gens, *args, **opts):
r"""
The submodule of the ambient space spanned by a finite set
of generators ``gens`` (as a submodule).

INPUT:

- ``gens`` -- a list or family of elements of ``self``

For additional optional arguments, see
:meth:`ModulesWithBasis.ParentMethods.submodule`.

EXAMPLES::

sage: X = CombinatorialFreeModule(QQ, range(4), prefix='X'); x = X.basis()
sage: F = X.submodule([x[0]-x[1], x[1]-x[2], x[2]-x[3]], prefix='F'); f = F.basis()
sage: U = F.submodule([f[0] + 2*f[1] - 5*f[2], f[1] + 2*f[2]]); U
Free module generated by {0, 1} over Rational Field
sage: [U.lift(u) for u in U.basis()]
[F[0] - 9*F[2], F[1] + 2*F[2]]
sage: V = F.subspace([f[0] + 2*f[1] - 5*f[2], f[1] + 2*f[2]]); V
Free module generated by {0, 1} over Rational Field
sage: [V.lift(u) for u in V.basis()]
[X[0] - 9*X[2] + 8*X[3], X[1] + 2*X[2] - 3*X[3]]
"""
gens = [self._ambient(g) for g in gens]
return self._ambient.submodule(gens, *args, **opts)
Loading