Skip to content
84 changes: 77 additions & 7 deletions src/sage/combinat/free_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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):
...
Expand All @@ -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::
Expand Down Expand Up @@ -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
Expand Down