# HG changeset patch
# User Jason Bandlow <jbandlow at gmail.com>
# Date 1263305729 18000
# Node ID ded8898437418b80f99152312ee7a888d46a7f07
# Parent  db7143738e1dbcb0c5415884ebaeee4500da1520
#7914: Implementation of triangular morphisms for modules with basis
imported patch triangular-morphisms-jb.patch

diff --git a/sage/categories/modules_with_basis.py b/sage/categories/modules_with_basis.py
--- a/sage/categories/modules_with_basis.py
+++ b/sage/categories/modules_with_basis.py
@@ -13,6 +13,8 @@ from sage.categories.all import Modules,
 from category_types import Category_over_base_ring
 from sage.misc.lazy_attribute import lazy_attribute
 from sage.misc.cachefunc import cached_method
+from sage.misc.misc import attrcall
+from sage.misc.sage_itertools import max_cmp, min_cmp
 from sage.structure.element import ModuleElement
 from sage.categories.morphism import SetMorphism, Morphism
 from sage.categories.homset import Hom
@@ -132,7 +134,7 @@ class ModulesWithBasis(Category_over_bas
 
             sage: C = ModulesWithBasis(QQ)
 
-        ``x`` is returned unchanged if it is readilly in this category::
+        ``x`` is returned unchanged if it is already in this category::
 
             sage: C(CombinatorialFreeModule(QQ, ('a','b','c')))
             Free module generated by {'a', 'b', 'c'} over Rational Field
@@ -175,19 +177,24 @@ class ModulesWithBasis(Category_over_bas
 
     # TODO: find something better to get this inheritance from CategoryWithTensorProduct.Element
     class ParentMethods(CategoryWithTensorProduct.ParentMethods, CategoryWithCartesianProduct.ParentMethods):
-        def module_morphism(self, on_basis = None, diagonal = None,  **keywords):
+        def module_morphism(self, on_basis = None, diagonal = None, triangular = None, **keywords):
             """
             Constructs functions by linearity
 
             INPUT:
-             - self: a parent `X` in ModulesWithBasis(R), with basis `x` indexed by `I`
-             - codomain: the codomain `Y` of f: defaults to `f.codomain` if the later is defined
-             - zero: the zero of the codomain; defaults to codomain.zero() or 0 if codomain is not specified
-             - position: a non negative integer; defaults to 0
-             - on_basis: a function `f` which accepts elements of `I`
+            
+             - ``self`` - a parent `X` in ModulesWithBasis(R), with basis `x`
+               indexed by `I`
+             - ``codomain`` - the codomain `Y` of f: defaults to ``f.codomain``
+               if the later is defined
+             - ``zero`` - the zero of the codomain; defaults to
+               `codomain.zero()` or 0 if codomain is not specified
+             - ``position`` - a non negative integer; defaults to 0
+             - ``on_basis`` - a function `f` which accepts elements of `I`
                as position-th argument and returns  elements of `Y`
-             - diagonal: a function `d` from `I` to `R`
-             - category: a category. By default, this is
+             - ``diagonal`` - a function `d` from `I` to `R`
+             - ``triangular`` a boolean (default: False)
+             - ``category`` - a category. By default, this is
                ``ModulesWithBasis(R)`` if `Y` is in this category, and
                otherwise this lets `Hom(X,Y)` decide
 
@@ -221,16 +228,50 @@ class ModulesWithBasis(Category_over_bas
             This assumes that the respective bases `x` and `y` of `X`
             and `Y` have the same index set `I`.
 
+            With ``triangular = upper``, the constructed module
+            morphism is assumed to be upper triangular; that is its
+            matrix in the distinguished basis of `X` and `Y` would be
+            upper triangular with invertible elements on its
+            diagonal. This currently assumes that `X` and `Y` have the
+            same index set `I`. This is used to compute preimages and
+            inverting the morphism::
+
+                sage: I = range(1,200)
+                sage: X = CombinatorialFreeModule(QQ, I); X.rename("X"); x = X.basis()
+                sage: Y = CombinatorialFreeModule(QQ, I); Y.rename("Y"); y = Y.basis()
+                sage: f = Y.sum_of_monomials * divisors
+                sage: phi = X.module_morphism(f, triangular="upper", codomain = Y)
+                sage: phi(x[2])
+                B[1] + B[2]
+                sage: phi(x[6])
+                B[1] + B[2] + B[3] + B[6]
+                sage: phi(x[30])
+                B[1] + B[2] + B[3] + B[5] + B[6] + B[10] + B[15] + B[30]
+                sage: phi.preimage(y[2])
+                -B[1] + B[2]
+                sage: phi.preimage(y[6])
+                B[1] - B[2] - B[3] + B[6]
+                sage: phi.preimage(y[30])
+                -B[1] + B[2] + B[3] + B[5] - B[6] - B[10] - B[15] + B[30]
+                sage: (phi^-1)(y[30])
+                -B[1] + B[2] + B[3] + B[5] - B[6] - B[10] - B[15] + B[30]
+
+            For details and further optional arguments, see
+            :class:`sage.categories.modules_with_basis.TriangularModuleMorphism`.
+
 
             Caveat: the returned element is in ``Hom(codomain, domain,
             category``). This is only correct for unary functions.
 
-            Todo: should codomain be self by default in the diagonal case?
+            Todo: should codomain be ``self`` by default in the
+            diagonal and triangular cases?
 
             """
             if diagonal is not None:
                 return DiagonalModuleMorphism(diagonal = diagonal, domain = self, **keywords)
             elif on_basis is not None:
+                if triangular is not None:
+                    return TriangularModuleMorphism(on_basis, domain = self, triangular = triangular, **keywords)
                 return ModuleMorphismByLinearity(on_basis = on_basis, domain = self, **keywords)
             else:
                 raise ValueError("module morphism requires either on_basis or diagonal argument")
@@ -239,6 +280,16 @@ class ModulesWithBasis(Category_over_bas
 
     # TODO: find something better to get this inheritance from CategoryWithTensorProduct.Element
     class ElementMethods(CategoryWithTensorProduct.ElementMethods, CategoryWithCartesianProduct.ElementMethods):
+        # TODO: Define the appropriate element methods here (instead of in
+        # subclasses).  These methods should be consistent with those on
+        # polynomials.
+
+#         def _neg_(self):
+#             """
+#             Default implementation of negation by trying to multiply by -1.
+#             TODO: doctest
+#             """
+#             return self._lmul_(-self.parent().base_ring().one(), self)
 
         def support_of_term(self):
             """
@@ -269,11 +320,291 @@ class ModulesWithBasis(Category_over_bas
             else:
                 raise ValueError, "%s is not a single term"%(self)
 
-#         def _neg_(self):
-#             """
-#             Default implementation of negation by trying to multiply by -1.
-#             """
-#             return self._lmul_(-self.parent().base_ring().one(), self)
+        def leading_support(self, cmp=None):
+            r"""
+            Returns the maximal element of the support of ``self``. Note
+            that this may not be the term which actually appears first when
+            ``self`` is printed.
+
+            If the default ordering of the basis elements is not what is
+            desired, a comparison function, ``cmp(x,y)``, can be provided.
+            This should return a negative value if `x < y`, `0` if `x == y`
+            and a positive value if `x > y`.
+
+            EXAMPLES::
+
+                sage: X = CombinatorialFreeModule(QQ, [1, 2, 3]); X.rename("X"); x = X.basis()
+                sage: x = 3*X.monomial(1) + 2*X.monomial(2) + 4*X.monomial(3)
+                sage: x.leading_support()
+                3
+                sage: def cmp(x,y): return y-x
+                sage: x.leading_support(cmp=cmp)
+                1
+
+                sage: s = SymmetricFunctions(QQ).schur()
+                sage: f = 2*s[1] + 3*s[2,1] - 5*s[3]
+                sage: f.leading_support()
+                [3]
+            """
+            return max_cmp(self.support(), cmp)
+
+
+        def leading_item(self, cmp=None):
+            r"""
+            Returns the pair ``(k, c)`` where ``c`` * (the basis elt. indexed
+            by ``k``) is the leading term of ``self``.
+
+            'leading term' means that the corresponding basis element is
+	    maximal.  Note that this may not be the term which actually appears
+	    first when ``self`` is printed.  If the default term ordering is not
+	    what is desired, a comparison function, ``cmp(x,y)``, can be
+	    provided.  This should return a negative value if `x < y`, `0` if
+	    `x == y` and a positive value if `x > y`.
+
+            EXAMPLES::
+
+                sage: X = CombinatorialFreeModule(QQ, [1, 2, 3]); X.rename("X"); x = X.basis()
+                sage: x = 3*X.monomial(1) + 2*X.monomial(2) + 4*X.monomial(3)
+                sage: x.leading_item()
+                (3, 4)
+                sage: def cmp(x,y): return y-x
+                sage: x.leading_item(cmp=cmp)
+                (1, 3)
+
+                sage: s = SymmetricFunctions(QQ).schur()
+                sage: f = 2*s[1] + 3*s[2,1] - 5*s[3]
+                sage: f.leading_item()
+                ([3], -5)
+            """
+            k = self.leading_support(cmp=cmp)
+            return k, self[k]
+
+        def leading_monomial(self, cmp=None):
+            r"""
+            Returns the leading monomial of ``self``.
+
+            This is the monomial whose corresponding basis element is
+	    maximal. Note that this may not be the term which actually appears
+	    first when ``self`` is printed. If the default term ordering is not
+	    what is desired, a comparison function, cmp(x,y), can be provided.
+            This should return a negative value if x < y, 0 if x == y
+            and a positive value if x > y.
+
+            EXAMPLES::
+
+                sage: X = CombinatorialFreeModule(QQ, [1, 2, 3]); X.rename("X"); x = X.basis()
+                sage: x = 3*X.monomial(1) + 2*X.monomial(2) + X.monomial(3)
+                sage: x.leading_monomial()
+                B[3]
+                sage: def cmp(x,y): return y-x
+                sage: x.leading_monomial(cmp=cmp)
+                B[1]
+
+                sage: s = SymmetricFunctions(QQ).schur()
+                sage: f = 2*s[1] + 3*s[2,1] - 5*s[3]
+                sage: f.leading_monomial()
+                s[3]
+            """
+            return self.parent().monomial( self.leading_support(cmp=cmp) )
+
+        def leading_coefficient(self, cmp=None):
+            r"""
+            Returns the leading coefficient of ``self``.
+
+            This is the coefficient of the term whose corresponding basis element is
+	    maximal. Note that this may not be the term which actually appears
+	    first when ``self`` is printed.  If the default term ordering is not
+	    what is desired, a comparison function, cmp(x,y), can be provided.
+            This should return a negative value if x < y, 0 if x == y
+            and a positive value if x > y.
+
+            EXAMPLES::
+
+                sage: X = CombinatorialFreeModule(QQ, [1, 2, 3]); X.rename("X")
+                sage: x = 3*X.monomial(1) + 2*X.monomial(2) + X.monomial(3)
+                sage: x.leading_coefficient()
+                1
+                sage: def cmp(x,y): return y-x
+                sage: x.leading_coefficient(cmp=cmp)
+                3
+
+                sage: s = SymmetricFunctions(QQ).schur()
+                sage: f = 2*s[1] + 3*s[2,1] - 5*s[3]
+                sage: f.leading_coefficient()
+                -5
+            """
+            return self.leading_item(cmp=cmp)[1]
+
+        def leading_term(self, cmp=None):
+            r"""
+            Returns the leading term of ``self``.
+
+            This is the term whose corresponding basis element is
+	    maximal. Note that this may not be the term which actually appears
+	    first when ``self`` is printed. If the default term ordering is not
+	    what is desired, a comparison function, cmp(x,y), can be provided.
+            This should return a negative value if x < y, 0 if x == y 
+            and a positive value if x > y.
+
+            EXAMPLES::
+
+                sage: X = CombinatorialFreeModule(QQ, [1, 2, 3]); X.rename("X"); x = X.basis()
+                sage: x = 3*X.monomial(1) + 2*X.monomial(2) + X.monomial(3)
+                sage: x.leading_term()
+                B[3]
+                sage: def cmp(x,y): return y-x
+                sage: x.leading_term(cmp=cmp)
+                3*B[1]
+
+                sage: s = SymmetricFunctions(QQ).schur()
+                sage: f = 2*s[1] + 3*s[2,1] - 5*s[3]
+                sage: f.leading_term()
+                -5*s[3]
+            """
+            return self.parent().term(*self.leading_item(cmp=cmp))
+
+        def trailing_support(self, cmp=None):
+            r"""
+            Returns the minimal element of the support of ``self``. Note
+            that this may not be the term which actually appears last when
+            ``self`` is printed.
+
+            If the default ordering of the basis elements is not what is
+            desired, a comparison function, ``cmp(x,y)``, can be provided.
+            This should return a negative value if `x < y`, `0` if `x == y` 
+            and a positive value if `x > y`.
+
+            EXAMPLES::
+
+                sage: X = CombinatorialFreeModule(QQ, [1, 2, 3]); X.rename("X"); x = X.basis()
+                sage: x = 3*X.monomial(1) + 2*X.monomial(2) + 4*X.monomial(3)
+                sage: x.trailing_support()
+                1
+                sage: def cmp(x,y): return y-x
+                sage: x.trailing_support(cmp=cmp)
+                3
+
+                sage: s = SymmetricFunctions(QQ).schur()
+                sage: f = 2*s[1] + 3*s[2,1] - 5*s[3]
+                sage: f.trailing_support()
+                [1]
+            """
+            return min_cmp(self.support(), cmp)
+
+        def trailing_item(self, cmp=None):
+            r"""
+            Returns the pair (c, k) where c*self.parent().monomial(k)
+            is the trailing term of ``self``.
+
+            This is the monomial whose corresponding basis element is
+	    minimal. Note that this may not be the term which actually appears
+	    last when ``self`` is printed.  If the default term ordering is not
+	    what is desired, a comparison function cmp(x,y), can be provided.
+            This should return a negative value if x < y, 0 if x == y
+            and a positive value if x > y.
+
+            EXAMPLES::
+
+                sage: X = CombinatorialFreeModule(QQ, [1, 2, 3]); X.rename("X"); x = X.basis()
+                sage: x = 3*X.monomial(1) + 2*X.monomial(2) + X.monomial(3)
+                sage: x.trailing_item()
+                (1, 3)
+                sage: def cmp(x,y): return y-x
+                sage: x.trailing_item(cmp=cmp)
+                (3, 1)
+
+                sage: s = SymmetricFunctions(QQ).schur()
+                sage: f = 2*s[1] + 3*s[2,1] - 5*s[3]
+                sage: f.trailing_item()
+                ([1], 2)
+            """
+            k = self.trailing_support(cmp=cmp)
+            return k, self[k]
+
+        def trailing_monomial(self, cmp=None):
+            r"""
+            Returns the trailing monomial of ``self``.
+
+            This is the monomial whose corresponding basis element is
+	    minimal. Note that this may not be the term which actually appears
+	    last when ``self`` is printed. If the default term ordering is not
+	    what is desired, a comparison function cmp(x,y), can be provided.
+            This should return a negative value if x < y, 0 if x == y
+            and a positive value if x > y.
+
+            EXAMPLES::
+
+                sage: X = CombinatorialFreeModule(QQ, [1, 2, 3]); X.rename("X"); x = X.basis()
+                sage: x = 3*X.monomial(1) + 2*X.monomial(2) + X.monomial(3)
+                sage: x.trailing_monomial()
+                B[1]
+                sage: def cmp(x,y): return y-x
+                sage: x.trailing_monomial(cmp=cmp)
+                B[3]
+
+                sage: s = SymmetricFunctions(QQ).schur()
+                sage: f = 2*s[1] + 3*s[2,1] - 5*s[3]
+                sage: f.trailing_monomial()
+                s[1]
+            """
+            return self.parent().monomial( self.trailing_support(cmp=cmp) )
+
+        def trailing_coefficient(self, cmp=None):
+            r"""
+            Returns the trailing coefficient of ``self``.
+
+            This is the coefficient of the monomial whose corresponding basis element is
+	    minimal. Note that this may not be the term which actually appears
+	    last when ``self`` is printed. If the default term ordering is not
+	    what is desired, a comparison function cmp(x,y), can be provided.
+            This should return a negative value if x < y, 0 if x == y 
+            and a positive value if x > y.
+
+            EXAMPLES::
+
+                sage: X = CombinatorialFreeModule(QQ, [1, 2, 3]); X.rename("X"); x = X.basis()
+                sage: x = 3*X.monomial(1) + 2*X.monomial(2) + X.monomial(3)
+                sage: x.trailing_coefficient()
+                3
+                sage: def cmp(x,y): return y-x
+                sage: x.trailing_coefficient(cmp=cmp)
+                1
+
+                sage: s = SymmetricFunctions(QQ).schur()
+                sage: f = 2*s[1] + 3*s[2,1] - 5*s[3]
+                sage: f.trailing_coefficient()
+                2
+            """
+
+            return self.trailing_item(cmp=cmp)[1]
+
+        def trailing_term(self, cmp=None):
+            r"""
+            Returns the trailing term of ``self``.
+
+            This is the term whose corresponding basis element is
+	    minimal. Note that this may not be the term which actually appears
+	    last when ``self`` is printed. If the default term ordering is not
+	    what is desired, a comparison function cmp(x,y), can be provided.
+            This should return a negative value if x < y, 0 if x == y
+            and a positive value if x > y.
+
+            EXAMPLES::
+
+                sage: X = CombinatorialFreeModule(QQ, [1, 2, 3]); X.rename("X"); x = X.basis()
+                sage: x = 3*X.monomial(1) + 2*X.monomial(2) + X.monomial(3)
+                sage: x.trailing_term()
+                3*B[1]
+                sage: def cmp(x,y): return y-x
+                sage: x.trailing_term(cmp=cmp)
+                B[3]
+
+                sage: s = SymmetricFunctions(QQ).schur()
+                sage: f = 2*s[1] + 3*s[2,1] - 5*s[3]
+                sage: f.trailing_term()
+                2*s[1]
+            """
+            return self.parent().term( *self.trailing_item(cmp=cmp) )
 
     class HomCategory(HomCategory):
         """
@@ -601,6 +932,201 @@ class ModuleMorphismByLinearity(Morphism
     # To be cleaned up
     _call_ = __call__
 
+class TriangularModuleMorphism(ModuleMorphismByLinearity):
+    """
+    A class for triangular module morphisms; that is module morphisms
+    from `X` to `Y` whose matrix in the distinguished basis of `X` and
+    `Y` would be upper triangular with invertible elements on its
+    diagonal. This currently assumes that `X` and `Y` have the same
+    index set `I`. However, `I` needs not be finite.
+
+    See :meth:`module_morphism` of ModulesWithBasis
+
+    EXAMPLES::
+
+        sage: X = CombinatorialFreeModule(QQ, [1, 2, 3]); X.rename("X"); x = X.basis()
+        sage: def ut(i): return sum(j*x[j] for j in range(i,4))
+        sage: import __main__; __main__.ut = ut
+        sage: phi = X.module_morphism(ut, triangular="lower", codomain = X)
+        sage: phi(x[2])
+        2*B[2] + 3*B[3]
+        sage: phi.preimage(x[2])
+        1/2*B[2] - 1/2*B[3]
+        sage: phi(phi.preimage(x[2]))
+        B[2]
+    """
+
+    def __init__(self, on_basis, domain, triangular = "upper", unitriangular=False,
+                 codomain = None, category = None, cmp = None, inverse = None, **keywords):
+        """
+        INPUT:
+
+         - ``domain``, ``codomain`` - two modules with basis `F` and `G`
+         - ``on_basis`` - a function from the index set of the basis of `F` to the
+           elements of `G`
+         - ``unitriangular`` - boolean (default: False)
+         - ``triangular`` - "upper" or "lower" (default: "upper")
+             - "upper": if the `leading_support()`  of the image of `F(i)` is `i`, or
+             - "lower": if the `trailing_support()` of the image of `F(i)` is `i`.
+
+         - ``cmp`` - an optional comparison function on the index set `I` of the basis
+           (see :meth:`.leading_support` for details).
+
+        Assumptions:
+
+         - `F` and `G` have the same base ring `R`
+         - Their respective bases `f` and `g` have the same index set `I`
+
+        OUTPUT:
+            The triangular module morphism from `F` to `G` which maps `f_\lambda`
+            to `on_basis(\lambda)` and is extended by linearity.
+
+        EXAMPLES::
+
+                sage: I = range(1,200)
+                sage: X = CombinatorialFreeModule(QQ, I); X.rename("X"); x = X.basis()
+                sage: Y = CombinatorialFreeModule(QQ, I); Y.rename("Y"); y = Y.basis()
+                sage: f = Y.sum_of_monomials * divisors
+                sage: phi = X.module_morphism(f, triangular="upper", unitriangular = True, codomain = Y)
+                sage: phi(x[2])
+                B[1] + B[2]
+                sage: phi(x[6])
+                B[1] + B[2] + B[3] + B[6]
+                sage: phi(x[30])
+                B[1] + B[2] + B[3] + B[5] + B[6] + B[10] + B[15] + B[30]
+                sage: phi.preimage(y[2])
+                -B[1] + B[2]
+                sage: phi.preimage(y[6])
+                B[1] - B[2] - B[3] + B[6]
+                sage: phi.preimage(y[30])
+                -B[1] + B[2] + B[3] + B[5] - B[6] - B[10] - B[15] + B[30]
+                sage: (phi^-1)(y[30])
+                -B[1] + B[2] + B[3] + B[5] - B[6] - B[10] - B[15] + B[30]
+
+        TESTS::
+
+            sage: phi.__class__
+            <class 'sage.categories.modules_with_basis.TriangularModuleMorphism'>
+            sage: TestSuite(phi).run() # known issue; see ModuleMorphism above
+            Failure in _test_category:
+            ...
+            The following tests failed: _test_category
+        """
+        assert codomain is not None
+        assert domain.basis().keys() == codomain.basis().keys()
+        assert domain.base_ring()    == codomain.base_ring()
+        if category is None:
+            category = ModulesWithBasis(domain.base_ring())
+        if triangular == "upper":
+            self._dominant_item = attrcall("leading_item",  cmp)
+        else:
+            self._dominant_item = attrcall("trailing_item", cmp)
+        # We store those two just be able to pass them down to the inverse function
+        self._triangular = triangular
+        self._cmp = cmp
+
+        self._unitriangular = unitriangular
+        self._inverse = inverse
+        ModuleMorphismByLinearity.__init__(self, domain = domain, codomain = codomain, category = category)
+        self.on_basis = on_basis # should this be called on_basis (or _on_basis)?
+
+
+    def _on_basis(self, i):
+        """
+        Returns the image, by self, of the basis element indexed by `i`.
+
+        TESTS::
+
+            sage: X = CombinatorialFreeModule(QQ, [1, 2, 3]); x = X.basis()
+            sage: Y = CombinatorialFreeModule(QQ, [1, 2, 3]); y = Y.basis()
+            sage: f = lambda i: sum(  y[j] for j in range(i,4)  )
+            sage: phi = X.module_morphism(f, triangular="lower", codomain = Y)
+            sage: phi._on_basis(2)
+            B[2] + B[3]
+        """
+        return self.codomain()(self.on_basis(i))
+
+    def __invert__(self):
+        """
+        Returns the triangular morphism which is the inverse of `self`.
+
+        TESTS::
+            sage: X = CombinatorialFreeModule(QQ, [1, 2, 3]); x = X.basis()
+            sage: Y = CombinatorialFreeModule(QQ, [1, 2, 3]); y = Y.basis()
+            sage: uut = lambda i: sum(  y[j] for j in range(1,i+1)) # uni-upper
+            sage: ult = lambda i: sum(  y[j] for j in range(i,4)  ) # uni-lower
+            sage: ut =  lambda i: sum(j*y[j] for j in range(1,i+1)) # upper
+            sage: lt =  lambda i: sum(j*y[j] for j in range(i,4  )) # lower
+            sage: f_uut = X.module_morphism(uut, triangular="upper", unitriangular=True,  codomain = Y)
+            sage: f_ult = X.module_morphism(ult, triangular="lower", unitriangular=True,  codomain = Y)
+            sage: f_ut =  X.module_morphism(ut,  triangular="upper",                      codomain = Y)
+            sage: f_lt =  X.module_morphism(lt,  triangular="lower",                      codomain = Y)
+            sage: (~f_uut)(y[2])
+            -B[1] + B[2]
+            sage: (~f_ult)(y[2])
+            B[2] - B[3]
+            sage: (~f_ut)(y[2])
+            -1/2*B[1] + 1/2*B[2]
+            sage: (~f_lt)(y[2])
+            1/2*B[2] - 1/2*B[3]
+        """
+        if self._inverse is not None:
+            return self._inverse
+        return self.__class__( self._invert_on_basis,
+                domain = self.codomain(),               codomain = self.domain(),
+                unitriangular = self._unitriangular,  triangular = self._triangular,
+                cmp = self._cmp,
+                inverse = self,                       category = self.category_for())
+
+    def _invert_on_basis(self, i):
+        r"""
+        Returns the image, by the inverse of ``self``, of the basis element
+        indexed by ``i``.
+
+        TESTS::
+            sage: X = CombinatorialFreeModule(QQ, [1, 2, 3]); x = X.basis()
+            sage: Y = CombinatorialFreeModule(QQ, [1, 2, 3]); y = Y.basis()
+            sage: uut = lambda i: sum(  y[j] for j in range(i,4)  ) # uni-upper
+            sage: phi = X.module_morphism(uut, triangular=True, codomain = Y)
+            sage: phi._invert_on_basis(2)
+            B[2] - B[3]
+        """
+        return self.preimage( self.codomain().monomial(i) )
+
+    def preimage(self, f):
+        """
+        Returns the image of f by the inverse of ``self``.
+
+        TESTS::
+            sage: X = CombinatorialFreeModule(QQ, [1, 2, 3]); x = X.basis()
+            sage: Y = CombinatorialFreeModule(QQ, [1, 2, 3]); y = Y.basis()
+            sage: uut = lambda i: sum(  y[j] for j in range(i,4)  ) # uni-upper
+            sage: phi = X.module_morphism(uut, triangular=True, codomain = Y)
+            sage: phi.preimage(y[1] + y[2])
+            B[1] - B[3]
+        """
+        F = self.domain()
+        G = self.codomain()
+        map = self._on_basis
+        assert f in G
+
+        remainder = f
+
+        out = F.zero()
+        while not remainder.is_zero():
+            (j,c) = self._dominant_item(remainder)
+
+            s = map(j)
+            assert j == self._dominant_item(s)[0]
+
+            if not self._unitriangular:
+                c /= s[j]
+
+            remainder -= s._lmul_(c)
+            out       += F.term(j, c)
+
+        return out
+
 class DiagonalModuleMorphism(ModuleMorphismByLinearity):
     """
     A class for diagonal module morphisms.
@@ -663,7 +1189,7 @@ class DiagonalModuleMorphism(ModuleMorph
         TESTS::
 
             sage: X = CombinatorialFreeModule(QQ, [1, 2, 3]); X.rename("X"); x = X.basis()
-            sage: Y = CombinatorialFreeModule(QQ, [1, 2, 3]); X.rename("Y"); y = Y.basis()
+            sage: Y = CombinatorialFreeModule(QQ, [1, 2, 3]); Y.rename("Y"); y = Y.basis()
             sage: phi = X.module_morphism(diagonal = factorial, codomain = X)
             sage: phi._on_basis(3)
             6*B[3]
@@ -677,7 +1203,7 @@ class DiagonalModuleMorphism(ModuleMorph
         EXAMPLES::
 
             sage: X = CombinatorialFreeModule(QQ, [1, 2, 3]); X.rename("X"); x = X.basis()
-            sage: Y = CombinatorialFreeModule(QQ, [1, 2, 3]); X.rename("Y"); y = Y.basis()
+            sage: Y = CombinatorialFreeModule(QQ, [1, 2, 3]); Y.rename("Y"); y = Y.basis()
             sage: phi = X.module_morphism(diagonal = factorial, codomain = X)
             sage: phi_inv = ~phi
             sage: phi_inv
@@ -757,8 +1283,7 @@ class PointwiseInverseFunction(SageObjec
             sage: f = PointwiseInverseFunction(factorial)
             sage: f(0), f(1), f(2), f(3)
             (1, 1, 1/2, 1/6)
-            sage: loads(dumps(f)) == f
-            True
+            sage: TestSuite(f).run()
         """
         self._pointwise_inverse = f
 
diff --git a/sage/misc/misc_c.pyx b/sage/misc/misc_c.pyx
--- a/sage/misc/misc_c.pyx
+++ b/sage/misc/misc_c.pyx
@@ -124,7 +124,7 @@ def prod(x, z=None, Py_ssize_t recursion
         prod = z*prod
         
     return prod
-    
+
 
 cdef balanced_list_prod(L, Py_ssize_t offset, Py_ssize_t count, Py_ssize_t cutoff):
     """
diff --git a/sage/misc/sage_itertools.py b/sage/misc/sage_itertools.py
--- a/sage/misc/sage_itertools.py
+++ b/sage/misc/sage_itertools.py
@@ -28,3 +28,112 @@ def unique_merge(*lists):
     """
     return (k for k,g in itertools.groupby(heapq.merge(*lists)))
 
+def min_cmp(L, cmp=None):
+    """
+    Returns the smallest item of a list (or iterable) with respect to
+    a comparison function.
+
+    INPUT:
+        ``L``   -- an iterable
+        ``cmp`` -- an optional comparison function.
+
+    ``cmp(x, y)`` should return a negative value if `x < y`, `0` if
+    `x == y`, and a positive value if `x > y`.
+
+    OUTPUT: the smallest item of ``L`` with respect to ``cmp``.
+
+    EXAMPLES::
+
+        sage: from sage.misc.sage_itertools import min_cmp
+        sage: L = [1,-1,3,-1,3,2]
+        sage: min_cmp(L)
+        -1
+        sage: def mycmp(x,y): return y - x
+        sage: min_cmp(L, mycmp)
+        3
+
+    The input can be any iterable::
+
+        sage: min_cmp( (x^2 for x in L) )
+        1
+        sage: min_cmp( (x^2 for x in L), mycmp)
+        9
+
+    Computing the min of an empty iterable raises and error::
+
+        sage: min_cmp([])
+        Traceback (most recent call last):
+        ...
+        ValueError: min() arg is an empty sequence
+        sage: min_cmp([], mycmp)
+        Traceback (most recent call last):
+        ...
+        ValueError: min_cmp() arg is an empty sequence
+    """
+    if cmp is None:
+        return min(L) # Resort to Python's standard min
+
+    iterator = iter(L)
+    try:
+        m = iterator.next()
+    except StopIteration:
+        raise ValueError, "min_cmp() arg is an empty sequence"
+    for item in iterator:
+        if cmp(item, m) < 0:
+            m = item
+    return m
+
+def max_cmp(L, cmp=None):
+    """
+    Returns the largest item of a list (or iterable) with respect to a
+    comparison function.
+
+    INPUT:
+        ``L``   -- an iterable
+        ``cmp`` -- an optional comparison function.
+
+    ``cmp(x, y)`` should return a negative value if `x < y`, `0` if
+    `x == y`, and a positive value if `x > y`.
+
+    OUTPUT: the largest item of ``L`` with respect to ``cmp``.
+
+    EXAMPLES::
+
+        sage: from sage.misc.sage_itertools import max_cmp
+        sage: L = [1,-1,3,-1,3,2]
+        sage: max_cmp(L)
+        3
+        sage: def mycmp(x,y): return y - x
+        sage: max_cmp(L, mycmp)
+        -1
+
+    The input can be any iterable::
+
+        sage: max_cmp( (x^2 for x in L) )
+        9
+        sage: max_cmp( (x^2 for x in L), mycmp)
+        1
+
+    Computing the max of an empty iterable raises and error::
+
+        sage: max_cmp([])
+        Traceback (most recent call last):
+        ...
+        ValueError: max() arg is an empty sequence
+        sage: max_cmp([], mycmp)
+        Traceback (most recent call last):
+        ...
+        ValueError: max_cmp() arg is an empty sequence
+    """
+    if cmp is None:
+        return max(L) # Resort to Python's standard max
+
+    iterator = iter(L)
+    try:
+        m = iterator.next()
+    except StopIteration:
+        raise ValueError, "max_cmp() arg is an empty sequence"
+    for item in iterator:
+        if cmp(item, m) > 0:
+            m = item
+    return m
