Skip to content

Commit

Permalink
sagemathgh-36988: Adding support for sums, intersection, and equality…
Browse files Browse the repository at this point in the history
… of SubmodulesWithBasis

    
<!-- ^^^^^
Please provide a concise, informative and self-explanatory title.
Don't put issue numbers in there, do this in the PR body below.
For example, instead of "Fixes sagemath#1234" use "Introduce new method to
calculate 1+1"
-->
<!-- Describe your changes here in detail -->

Common vector subspace operations are taking sums, intersections, and
checking equality, but these are not implemented (inclusion already is).
We provide generic implementations of these for objects in
`ModulesWithBasis` and make a few other minor improvements.

<!-- Why is this change required? What problem does it solve? -->
<!-- If this PR resolves an open issue, please link to it here. For
example "Fixes sagemath#12345". -->
<!-- If your change requires a documentation PR, please link it
appropriately. -->

### 📝 Checklist

<!-- Put an `x` in all the boxes that apply. -->
<!-- If your change requires a documentation PR, please link it
appropriately -->
<!-- If you're unsure about any of these, don't hesitate to ask. We're
here to help! -->
<!-- Feel free to remove irrelevant items. -->

- [x] The title is concise, informative, and self-explanatory.
- [x] The description explains in detail what this PR is about.
- [x] I have linked a relevant issue or discussion.
- [x] I have created tests covering the changes.
- [x] I have updated the documentation accordingly.

### ⌛ Dependencies

<!-- List all open PRs that this PR logically depends on
- sagemath#12345: short description why this is a dependency
- sagemath#34567: ...
-->

<!-- If you're unsure about any of these, don't hesitate to ask. We're
here to help! -->
    
URL: sagemath#36988
Reported by: Travis Scrimshaw
Reviewer(s): Martin Rubey, Travis Scrimshaw
  • Loading branch information
Release Manager committed Feb 19, 2024
2 parents cffd006 + 51f1c54 commit a7fa70e
Show file tree
Hide file tree
Showing 2 changed files with 333 additions and 8 deletions.
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 @@ def lift(self, x):
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 @@ class SubmoduleWithBasis(CombinatorialFreeModule):
- :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 @@ def lift(self):
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 @@ def retract(self):
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 @@ def is_submodule(self, other):
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")
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)
if self.dimension() != other.dimension(): # quick dimension check
return False
if self not in ModulesWithBasis.FiniteDimensional:
raise NotImplementedError("only implemented for finite dimensional submodules")
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)

0 comments on commit a7fa70e

Please sign in to comment.