diff --git a/src/sage/categories/modules_with_basis.py b/src/sage/categories/modules_with_basis.py index df7a04edc1f..da8d22d8600 100644 --- a/src/sage/categories/modules_with_basis.py +++ b/src/sage/categories/modules_with_basis.py @@ -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``. diff --git a/src/sage/modules/with_basis/subquotient.py b/src/sage/modules/with_basis/subquotient.py index 0918c5a8b19..220e1e521f3 100644 --- a/src/sage/modules/with_basis/subquotient.py +++ b/src/sage/modules/with_basis/subquotient.py @@ -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""" @@ -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): @@ -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, @@ -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:: @@ -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. = 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)