diff --git a/src/sage/combinat/free_module.py b/src/sage/combinat/free_module.py index c8269e5f96f..ec020f64901 100644 --- a/src/sage/combinat/free_module.py +++ b/src/sage/combinat/free_module.py @@ -482,6 +482,29 @@ def construction(self): return VectorFunctor(None, True, None, with_basis='standard', basis_keys=self.basis().keys()), self.base_ring() + def change_ring(self, R): + r""" + Return the base change of ``self`` to `R`. + + EXAMPLES:: + + sage: F = CombinatorialFreeModule(ZZ, ['a','b','c']); F + Free module generated by {'a', 'b', 'c'} over Integer Ring + sage: F_QQ = F.change_ring(QQ); F_QQ + Free module generated by {'a', 'b', 'c'} over Rational Field + sage: F_QQ.change_ring(ZZ) == F + True + """ + if R is self.base_ring(): + return self + construction = self.construction() + if construction is not None: + functor, arg = construction + from sage.categories.pushout import VectorFunctor + if isinstance(functor, VectorFunctor): + return functor(R) + raise NotImplementedError('the method change_ring() has not yet been implemented') + # For backwards compatibility _repr_term = IndexedGenerators._repr_generator _latex_term = IndexedGenerators._latex_generator @@ -644,16 +667,19 @@ def _element_constructor_(self, x): are systematically defined (and mathematically meaningful) for algebras. - Conversions between distinct free modules are not allowed any - more:: + A coercion between free modules with the same indices exists + whenever a coercion map is defined between their base rings:: sage: F = CombinatorialFreeModule(ZZ, ["a", "b"]); F.rename("F") sage: G = CombinatorialFreeModule(QQ, ["a", "b"]); G.rename("G") - sage: H = CombinatorialFreeModule(ZZ, ["a", "b", "c"]); H.rename("H") sage: G(F.monomial("a")) - Traceback (most recent call last): - ... - TypeError: do not know how to make x (= B['a']) an element of self (=G) + B['a'] + sage: G(-3*F.monomial("a")) + -3*B['a'] + + Otherwise, there is no conversion between distinct free modules:: + + sage: H = CombinatorialFreeModule(ZZ, ["a", "b", "c"]); H.rename("H") sage: H(F.monomial("a")) Traceback (most recent call last): ... @@ -675,12 +701,13 @@ def _element_constructor_(self, x): sage: pp(a) 1/4*p[1, 1] # p[1, 1] + 1/4*p[1, 1] # p[2] + 1/4*p[2] # p[1, 1] + 1/4*p[2] # p[2] - Extensions of the ground ring should probably be reintroduced + General extensions of the ground ring should probably be reintroduced at some point, but via coercions, and with stronger sanity checks (ensuring that the codomain is really obtained by extending the scalar of the domain; checking that they share the same class is not sufficient). + TESTS: Conversion from the ground ring is implemented for algebras:: @@ -781,6 +808,49 @@ def _first_ngens(self, n): it = iter(self._indices) return tuple(B[next(it)] for i in range(n)) + def _coerce_map_from_(self, R): + """ + Return ``True`` if there is a coercion map from ``R`` into ``self``. + + There exists a coercion map from: + + - a free module whose base ring coerces into the base ring of + ``self``, and which has the same indices as ``self`` + + EXAMPLES:: + + sage: C = CombinatorialFreeModule(ZZ, Set([1,2])) + sage: CQ = CombinatorialFreeModule(QQ, Set([1,2])) + sage: CQ.has_coerce_map_from(C) + True + sage: c = C.monomial(2) + sage: cq = CQ(c); cq + B[2] + sage: cq.leading_coefficient().parent() + Rational Field + sage: C.has_coerce_map_from(CQ) + False + + sage: CF2 = CombinatorialFreeModule(GF(2), Set([1,2])) + sage: CF2.has_coerce_map_from(C) + True + sage: c = C.monomial(1) + sage: CF2(2*c) + 0 + sage: CF2(3*c) + B[1] + """ + if isinstance(R, CombinatorialFreeModule): + try: + CR = R.base_extend(self.base_ring()) + except (NotImplementedError, TypeError): + pass + else: + if CR == self: + return lambda parent, x: self._from_dict(x._monomial_coefficients, + coerce=True, remove_zeros=True) + return super()._coerce_map_from_(R) + def dimension(self): """ Return the dimension of the free module (which is given