# Morphisms of modules with a basis#

This module contains a hierarchy of classes for morphisms of modules with a basis (category Modules.WithBasis):

These are internal classes; it is recommended not to use them directly, and instead to construct morphisms through the ModulesWithBasis.ParentMethods.module_morphism() method of the domain, or through the homset. See the former for an overview of the possible arguments.

EXAMPLES:

We construct a morphism through the method ModulesWithBasis.ParentMethods.module_morphism(), by specifying the image of each element of the distinguished basis:

sage: X = CombinatorialFreeModule(QQ, [1,2,3]);   x = X.basis()
sage: Y = CombinatorialFreeModule(QQ, [1,2,3,4]); y = Y.basis()
sage: on_basis = lambda i: Y.monomial(i) + 2*Y.monomial(i+1)

sage: phi1 = X.module_morphism(on_basis, codomain=Y)
sage: phi1(x[1])
B[1] + 2*B[2]

sage: phi1
Generic morphism:
From: Free module generated by {1, 2, 3} over Rational Field
To:   Free module generated by {1, 2, 3, 4} over Rational Field
sage: phi1.parent()
Set of Morphisms from Free module generated by {1, 2, 3} over Rational Field to Free module generated by {1, 2, 3, 4} over Rational Field in Category of finite dimensional vector spaces with basis over Rational Field
sage: phi1.__class__
<class 'sage.modules.with_basis.morphism.ModuleMorphismByLinearity_with_category'>


Constructing the same morphism from the homset:

sage: H = Hom(X,Y)
sage: phi2 = H(on_basis=on_basis)
sage: phi1 == phi2
True


Constructing the same morphism directly using the class; no backward compatibility is guaranteed in this case:

sage: from sage.modules.with_basis.morphism import ModuleMorphismByLinearity
sage: phi3 = ModuleMorphismByLinearity(X, on_basis, codomain=Y)
sage: phi3 == phi1
True


Warning

The hierarchy of classes implemented in this module is one of the first non-trivial hierarchies of classes for morphisms. It is hitting a couple scaling issues:

• There are many independent properties from which module morphisms can get code (being defined by linearity, from a matrix, or a function; being triangular, being diagonal, …). How to mitigate the class hierarchy growth?

This will become even more stringent as more properties are added (e.g. being defined from generators for an algebra morphism, …)

Categories, whose primary purpose is to provide infrastructure for handling such large hierarchy of classes, can’t help at this point: there is no category whose morphisms are triangular morphisms, and it’s not clear such a category would be sensible.

• How to properly handle __init__ method calls and multiple inheritance?

• Who should be in charge of setting the default category: the classes themselves, or ModulesWithBasis.ParentMethods.module_morphism()?

Because of this, the hierarchy of classes, and the specific APIs, is likely to be refactored as better infrastructure and best practices emerge.

AUTHORS:

• Nicolas M. Thiery (2008-2015)

• Jason Bandlow and Florent Hivert (2010): Triangular Morphisms

• Christian Stump (2010): github issue #9648 module_morphism’s to a wider class of codomains

Before github issue #8678, this hierarchy of classes used to be in sage.categories.modules_with_basis; see github issue #8678 for the complete log.

class sage.modules.with_basis.morphism.DiagonalModuleMorphism(domain, diagonal, codomain=None, category=None)#

A class for diagonal module morphisms.

INPUT:

• domain, codomain – two modules with basis $$F$$ and $$G$$, respectively

• diagonal – a function $$d$$

Assumptions:

• domain and codomain have the same base ring $$R$$,

• their respective bases $$F$$ and $$G$$ have the same index set $$I$$,

• $$d$$ is a function $$I \to R$$.

Return the diagonal module morphism from domain to codomain sending $$F(i) \mapsto d(i) G(i)$$ for all $$i \in I$$.

By default, codomain is currently assumed to be domain. (Todo: make a consistent choice with *ModuleMorphism.)

Todo

• Implement an optimized _call_() function.

• Generalize to a mapcoeffs.

• Generalize to a mapterms.

EXAMPLES:

sage: X = CombinatorialFreeModule(QQ, [1, 2, 3]); X.rename("X")
sage: phi = X.module_morphism(diagonal=factorial, codomain=X)
sage: x = X.basis()
sage: phi(x[1]), phi(x[2]), phi(x[3])
(B[1], 2*B[2], 6*B[3])

class sage.modules.with_basis.morphism.ModuleMorphism(domain, codomain=None, category=None, affine=False)#

Bases: Morphism

The top abstract base class for module with basis morphisms.

INPUT:

• domain – a parent in ModulesWithBasis(...)

• codomain – a parent in Modules(...);

• category – a category or None (default: $$None$$)

• affine – whether we define an affine module morphism (default: False).

Construct a module morphism from domain to codomain in the category category. By default, the category is the first of Modules(R).WithBasis().FiniteDimensional(), Modules(R).WithBasis(), Modules(R), CommutativeAdditiveMonoids() that contains both the domain and the codomain. If initializing an affine morphism, then $$Sets()$$ is used instead.

The role of this class is minimal: it provides an __init__() method which:

• handles the choice of the default category

• handles the proper inheritance from categories by updating the class of self upon construction.

class sage.modules.with_basis.morphism.ModuleMorphismByLinearity(domain, on_basis=None, codomain=None, category=None, position=0, zero=None)#

Bases: ModuleMorphism

A class for module morphisms obtained by extending a function by linearity.

INPUT:

• domain, codomain, category – as for ModuleMorphism

• on_basis – a function which accepts indices of the basis of domain as position-th argument

• codomain – a parent in Modules(...)

(default: on_basis.codomain())

• position – a non-negative integer (default: 0)

• zero – the zero of the codomain (defaults: codomain.zero())

Note

on_basis may alternatively be provided in derived classes by passing None as argument, and implementing or setting the attribute _on_basis

on_basis()#

Return the action of this morphism on basis elements, as per ModulesWithBasis.Homsets.ElementMethods.on_basis().

OUTPUT:

• a function from the indices of the basis of the domain to the codomain

EXAMPLES:

sage: X = CombinatorialFreeModule(ZZ, [-2, -1, 1, 2])
sage: Y = CombinatorialFreeModule(ZZ, [1, 2])
sage: phi_on_basis = Y.monomial * abs
sage: phi = sage.modules.with_basis.morphism.ModuleMorphismByLinearity(X, on_basis = phi_on_basis, codomain=Y)
sage: x = X.basis()
sage: phi.on_basis()(-2)
B[2]
sage: phi.on_basis() == phi_on_basis
True

class sage.modules.with_basis.morphism.ModuleMorphismFromFunction(domain, function, codomain=None, category=None)#

A class for module morphisms implemented by a plain function.

INPUT:

• domain, codomain, category – as for ModuleMorphism

• function – any function or callable from domain to codomain

class sage.modules.with_basis.morphism.ModuleMorphismFromMatrix(domain, matrix, codomain=None, category=None, side='left')#

A class for module morphisms built from a matrix in the distinguished bases of the domain and codomain.

INPUT:

• domain, codomain – two finite dimensional modules over the same base ring $$R$$ with basis $$F$$ and $$G$$, respectively

• matrix – a matrix with base ring $$R$$ and dimensions matching that of $$F$$ and $$G$$, respectively

• side – “left” or “right” (default: “left”)

If side is “left”, this morphism is considered as acting on the left; i.e. each column of the matrix represents the image of an element of the basis of the domain.

• category – a category or None (default: None)

EXAMPLES:

sage: X = CombinatorialFreeModule(ZZ, [1,2]); X.rename("X"); x = X.basis()
sage: Y = CombinatorialFreeModule(ZZ, [3,4]); Y.rename("Y"); y = Y.basis()
sage: m = matrix([[1,2],[3,5]])
sage: phi = X.module_morphism(matrix=m, codomain=Y)
sage: phi.parent()
Set of Morphisms from X to Y in Category of finite dimensional modules with basis over Integer Ring
sage: phi.__class__
<class 'sage.modules.with_basis.morphism.ModuleMorphismFromMatrix_with_category'>
sage: phi(x[1])
B[3] + 3*B[4]
sage: phi(x[2])
2*B[3] + 5*B[4]

sage: m = matrix([[1,2],[3,5]])
sage: phi = X.module_morphism(matrix=m, codomain=Y, side="right",
....:                         category=Modules(ZZ).WithBasis())
sage: phi.parent()
Set of Morphisms from X to Y
in Category of modules with basis over Integer Ring
sage: phi(x[1])
B[3] + 2*B[4]
sage: phi(x[2])
3*B[3] + 5*B[4]


Todo

Possibly implement rank, addition, multiplication, matrix, etc, from the stored matrix.

class sage.modules.with_basis.morphism.PointwiseInverseFunction(f)#

Bases: SageObject

A class for pointwise inverse functions.

The pointwise inverse function of a function $$f$$ is the function sending every $$x$$ to $$1 / f(x)$$.

EXAMPLES:

sage: from sage.modules.with_basis.morphism import PointwiseInverseFunction
sage: f = PointwiseInverseFunction(factorial)
sage: f(0), f(1), f(2), f(3)
(1, 1, 1/2, 1/6)

pointwise_inverse()#
class sage.modules.with_basis.morphism.TriangularModuleMorphism(triangular='upper', unitriangular=False, key=None, inverse=None, inverse_on_support=<built-in function identity>, invertible=None)#

Bases: ModuleMorphism

An abstract class for triangular module morphisms

Let $$X$$ and $$Y$$ be modules over the same base ring, with distinguished bases $$F$$ indexed by $$I$$ and $$G$$ indexed by $$J$$, respectively.

A module morphism $$\phi$$ from $$X$$ to $$Y$$ is triangular if its representing matrix in the distinguished bases of $$X$$ and $$Y$$ is upper triangular (echelon form).

More precisely, $$\phi$$ is upper triangular w.r.t. a total order $$<$$ on $$J$$ if, for any $$j\in J$$, there exists at most one index $$i\in I$$ such that the leading support of $$\phi(F_i)$$ is $$j$$ (see leading_support()). We denote by $$r(j)$$ this index, setting $$r(j)$$ to None if it does not exist.

Lower triangular morphisms are defined similarly, taking the trailing support instead (see trailing_support()).

A triangular morphism is unitriangular if all its pivots (i.e. coefficient of $$j$$ in each $$\phi(F[r(j)])$$) are $$1$$.

INPUT:

• domain – a module with basis $$X$$

• codomain – a module with basis $$Y$$ (default: $$X$$)

• category – a category, as for ModuleMorphism

• triangular"upper" or "lower" (default: "upper")

• unitriangular – boolean (default: False) As a shorthand, one may use unitriangular="lower" for triangular="lower", unitriangular=True.

• key – a comparison key on $$J$$ (default: the usual comparison of elements of $$J$$)

• inverse_on_support – a function $$J \to I\cup \{None\}$$ implementing $$r$$ (default: the identity function). If set to “compute”, the values of $$r(j)$$ are precomputed by running through the index set $$I$$ of the basis of the domain. This of course requires the domain to be finite dimensional.

• invertible – a boolean or None (default: None); can be set to specify that $$\phi$$ is known to be (or not to be) invertible. If the domain and codomain share the same indexing set, this is by default automatically set to True if inverse_on_support is the identity, or in the finite dimensional case.

OUTPUT:

A morphism from $$X$$ to $$Y$$.

Warning

This class is meant to be used as a complement for a concrete morphism class. In particular, the __init__() method focuses on setting up the data structure describing the triangularity of the morphism. It purposely does not call ModuleMorphism.__init__() which should be called (directly or indirectly) beforehand.

EXAMPLES:

We construct and invert an upper unitriangular module morphism between two free $$\QQ$$-modules:

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: ut = Y.sum_of_monomials * divisors   # This * is map composition.
sage: phi = X.module_morphism(ut, unitriangular="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]


A lower triangular (but not unitriangular) morphism:

sage: X = CombinatorialFreeModule(QQ, [1, 2, 3]); X.rename("X"); x = X.basis()
sage: def lt(i): return sum(j*x[j] for j in range(i,4))
sage: phi = X.module_morphism(lt, 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]


Using the key keyword, we can use triangularity even if the map becomes triangular only after a permutation of the basis:

sage: X = CombinatorialFreeModule(QQ, [1, 2, 3]); X.rename("X"); x = X.basis()
sage: def ut(i): return (x[1] + x[2] if i == 1 else x[2] + (x[3] if i == 3 else 0))
sage: perm = [0, 2, 1, 3]
sage: phi = X.module_morphism(ut, triangular="upper", codomain=X,
....:                         key=lambda a: perm[a])
sage: [phi(x[i]) for i in range(1, 4)]
[B[1] + B[2], B[2], B[2] + B[3]]
sage: [phi.preimage(x[i]) for i in range(1, 4)]
[B[1] - B[2], B[2], -B[2] + B[3]]


The same works in the lower-triangular case:

sage: def lt(i): return (x[1] + x[2] + x[3] if i == 2 else x[i])
sage: phi = X.module_morphism(lt, triangular="lower", codomain=X,
....:                         key=lambda a: perm[a])
sage: [phi(x[i]) for i in range(1, 4)]
[B[1], B[1] + B[2] + B[3], B[3]]
sage: [phi.preimage(x[i]) for i in range(1, 4)]
[B[1], -B[1] + B[2] - B[3], B[3]]


An injective but not surjective morphism cannot be inverted, but the inverse_on_support keyword allows Sage to find a partial inverse:

sage: X = CombinatorialFreeModule(QQ, [1,2,3]); x = X.basis()
sage: Y = CombinatorialFreeModule(QQ, [1,2,3,4,5]); y = Y.basis()
sage: ult = lambda i: sum(  y[j] for j in range(i+1,6)  )
sage: phi = X.module_morphism(ult, unitriangular="lower", codomain=Y,
....:        inverse_on_support=lambda i: i-1 if i in [2,3,4] else None)
sage: phi(x[2])
B[3] + B[4] + B[5]
sage: phi.preimage(y[3])
B[2] - B[3]


The inverse_on_support keyword can also be used if the bases of the domain and the codomain are identical but one of them has to be permuted in order to render the morphism triangular. For example:

sage: X = CombinatorialFreeModule(QQ, [1, 2, 3]); X.rename("X"); x = X.basis()
sage: def ut(i):
....:     return (x[3] if i == 1 else x[1] if i == 2
....:             else x[1] + x[2])
sage: def perm(i):
....:     return (2 if i == 1 else 3 if i == 2 else 1)
sage: phi = X.module_morphism(ut, triangular="upper", codomain=X,
....:                         inverse_on_support=perm)
sage: [phi(x[i]) for i in range(1, 4)]
[B[3], B[1], B[1] + B[2]]
sage: [phi.preimage(x[i]) for i in range(1, 4)]
[B[2], -B[2] + B[3], B[1]]


The same works if the permutation induces lower triangularity:

sage: X = CombinatorialFreeModule(QQ, [1, 2, 3]); X.rename("X"); x = X.basis()
sage: def lt(i):
....:     return (x[3] if i == 1 else x[2] if i == 2
....:             else x[1] + x[2])
sage: def perm(i):
....:     return 4 - i
sage: phi = X.module_morphism(lt, triangular="lower", codomain=X,
....:                         inverse_on_support=perm)
sage: [phi(x[i]) for i in range(1, 4)]
[B[3], B[2], B[1] + B[2]]
sage: [phi.preimage(x[i]) for i in range(1, 4)]
[-B[2] + B[3], B[2], B[1]]


In the finite dimensional case, one can ask Sage to recover inverse_on_support by a precomputation:

sage: X = CombinatorialFreeModule(QQ, [1, 2, 3]); x = X.basis()
sage: Y = CombinatorialFreeModule(QQ, [1, 2, 3, 4]); y = Y.basis()
sage: ut = lambda i: sum(  y[j] for j in range(1,i+2) )
sage: phi = X.module_morphism(ut, triangular="upper", codomain=Y,
....:                         inverse_on_support="compute")
sage: tx = "{} {} {}"
sage: for j in Y.basis().keys():
....:     i = phi._inverse_on_support(j)
....:     print(tx.format(j, i, phi(x[i]) if i is not None else None))
1 None None
2 1 B[1] + B[2]
3 2 B[1] + B[2] + B[3]
4 3 B[1] + B[2] + B[3] + B[4]


The inverse_on_basis and key keywords can be combined:

sage: X = CombinatorialFreeModule(QQ, [1, 2, 3]); X.rename("X")
sage: x = X.basis()
sage: def ut(i):
....:     return (2*x[2] + 3*x[3] if i == 1
....:             else x[1] + x[2] + x[3] if i == 2
....:             else 4*x[2])
sage: def perm(i):
....:     return (2 if i == 1 else 3 if i == 2 else 1)
sage: perverse_key = lambda a: (a - 2) % 3
sage: phi = X.module_morphism(ut, triangular="upper", codomain=X,
....:                         inverse_on_support=perm, key=perverse_key)
sage: [phi(x[i]) for i in range(1, 4)]
[2*B[2] + 3*B[3], B[1] + B[2] + B[3], 4*B[2]]
sage: [phi.preimage(x[i]) for i in range(1, 4)]
[-1/3*B[1] + B[2] - 1/12*B[3], 1/4*B[3], 1/3*B[1] - 1/6*B[3]]

cokernel_basis_indices()#

Return the indices of the natural monomial basis of the cokernel of self.

INPUT:

• self – a triangular morphism over a field or a unitriangular morphism over a ring, with a finite dimensional codomain.

OUTPUT:

A list $$E$$ of indices of the basis $$(B_e)_e$$ of the codomain of self so that $$(B_e)_{e\in E}$$ forms a basis of a supplementary of the image set of self.

Thinking of this triangular morphism as a row echelon matrix, this returns the complementary of the characteristic columns. Namely $$E$$ is the set of indices which do not appear as leading support of some element of the image set of self.

EXAMPLES:

sage: X = CombinatorialFreeModule(ZZ, [1,2,3]); x = X.basis()
sage: Y = CombinatorialFreeModule(ZZ, [1,2,3,4,5]); y = Y.basis()
sage: uut = lambda i: sum(  y[j] for j in range(i+1,6)  ) # uni-upper
sage: phi = X.module_morphism(uut, unitriangular="upper", codomain=Y,
....:       inverse_on_support=lambda i: i-1 if i in [2,3,4] else None)
sage: phi.cokernel_basis_indices()
[1, 5]

sage: phi = X.module_morphism(uut, triangular="upper", codomain=Y,
....:       inverse_on_support=lambda i: i-1 if i in [2,3,4] else None)
sage: phi.cokernel_basis_indices()
Traceback (most recent call last):
...
NotImplementedError: cokernel_basis_indices for a triangular but not unitriangular morphism over a ring

sage: Y = CombinatorialFreeModule(ZZ, NN); y = Y.basis()
sage: phi = X.module_morphism(uut, unitriangular="upper", codomain=Y,
....:       inverse_on_support=lambda i: i-1 if i in [2,3,4] else None)
sage: phi.cokernel_basis_indices()
Traceback (most recent call last):
...
NotImplementedError: cokernel_basis_indices implemented only for morphisms with a finite dimensional codomain

cokernel_projection(category=None)#

Return a projection on the co-kernel of self.

INPUT:

• category – the category of the result

EXAMPLES:

sage: X = CombinatorialFreeModule(QQ, [1,2,3]); x = X.basis()
sage: Y = CombinatorialFreeModule(QQ, [1,2,3,4,5]); y = Y.basis()
sage: lt = lambda i: sum(  y[j] for j in range(i+1,6)  ) # lower
sage: phi = X.module_morphism(lt, triangular="lower", codomain=Y,
....:      inverse_on_support=lambda i: i-1 if i in [2,3,4] else None)
sage: phipro = phi.cokernel_projection()
sage: phipro(y[1] + y[2])
B[1]
sage: all(phipro(phi(x)).is_zero() for x in X.basis())
True
sage: phipro(y[1])
B[1]
sage: phipro(y[4])
-B[5]
sage: phipro(y[5])
B[5]

coreduced(y)#

Return $$y$$ reduced w.r.t. the image of self.

INPUT:

• self – a triangular morphism over a field, or a unitriangular morphism over a ring

• y – an element of the codomain of self

Suppose that self is a morphism from $$X$$ to $$Y$$. Then, for any $$y \in Y$$, the call self.coreduced(y) returns a normal form for $$y$$ in the quotient $$Y / I$$ where $$I$$ is the image of self.

EXAMPLES:

sage: X = CombinatorialFreeModule(QQ, [1,2,3]); x = X.basis()
sage: Y = CombinatorialFreeModule(QQ, [1,2,3,4,5]); y = Y.basis()
sage: ult = lambda i: sum(  y[j] for j in range(i+1,6)  )
sage: phi = X.module_morphism(ult, unitriangular="lower", codomain=Y,
....:        inverse_on_support=lambda i: i-1 if i in [2,3,4] else None)
sage: [phi(v) for v in X.basis()]
[B[2] + B[3] + B[4] + B[5],
B[3] + B[4] + B[5],
B[4] + B[5]]
sage: [phi.coreduced(y[1]-2*y[4])]
[B[1] + 2*B[5]]
sage: [phi.coreduced(v) for v in y]
[B[1], 0, 0, -B[5], B[5]]


Now with a non unitriangular morphism:

sage: lt = lambda i: sum( j*y[j] for j in range(i+1,6)  )
sage: phi = X.module_morphism(lt, triangular="lower", codomain=Y,
....:       inverse_on_support=lambda i: i-1 if i in [2,3,4] else None)
sage: [phi(v) for v in X.basis()]
[2*B[2] + 3*B[3] + 4*B[4] + 5*B[5],
3*B[3] + 4*B[4] + 5*B[5],
4*B[4] + 5*B[5]]
sage: [phi.coreduced(y[1]-2*y[4])]
[B[1] + 5/2*B[5]]
sage: [phi.coreduced(v) for v in y]
[B[1], 0, 0, -5/4*B[5], B[5]]


For general rings, this method is only implemented for unitriangular morphisms:

sage: X = CombinatorialFreeModule(ZZ, [1,2,3]); x = X.basis()
sage: Y = CombinatorialFreeModule(ZZ, [1,2,3,4,5]); y = Y.basis()
sage: phi = X.module_morphism(ult, unitriangular="lower", codomain=Y,
....:       inverse_on_support=lambda i: i-1 if i in [2,3,4] else None)
sage: [phi.coreduced(y[1]-2*y[4])]
[B[1] + 2*B[5]]
sage: [phi.coreduced(v) for v in y]
[B[1], 0, 0, -B[5], B[5]]

sage: phi = X.module_morphism(lt, triangular="lower", codomain=Y,
....:       inverse_on_support=lambda i: i-1 if i in [2,3,4] else None)
sage: [phi.coreduced(v) for v in y]
Traceback (most recent call last):
...
NotImplementedError: coreduce for a triangular but not unitriangular morphism over a ring


Note

Before github issue #8678 this method used to be called co_reduced.

preimage(f)#

Return the preimage of $$f$$ under self.

EXAMPLES:

sage: X = CombinatorialFreeModule(QQ, [1, 2, 3]); x = X.basis()
sage: Y = CombinatorialFreeModule(QQ, [1, 2, 3]); y = Y.basis()
sage: ult = lambda i: sum(  y[j] for j in range(i,4)  ) # uni-lower
sage: phi = X.module_morphism(ult, triangular="lower", codomain=Y)
sage: phi.preimage(y[1] + y[2])
B[1] - B[3]


The morphism need not be surjective. In the following example, the codomain is of larger dimension than the domain:

sage: X = CombinatorialFreeModule(QQ, [1, 2, 3]); x = X.basis()
sage: Y = CombinatorialFreeModule(QQ, [1, 2, 3, 4]); y = Y.basis()
sage: lt = lambda i: sum(  y[j] for j in range(i,5)  )
sage: phi = X.module_morphism(lt, triangular="lower", codomain=Y)
sage: phi.preimage(y[1] + y[2])
B[1] - B[3]


Here are examples using inverse_on_support to handle a morphism that shifts the leading indices by $$1$$:

sage: X = CombinatorialFreeModule(QQ, [1, 2, 3]); x = X.basis()
sage: Y = CombinatorialFreeModule(QQ, [1, 2, 3, 4, 5]); y = Y.basis()
sage: lt = lambda i: sum(  y[j] for j in range(i+1,6)  ) # lower
sage: phi = X.module_morphism(lt, triangular="lower", codomain=Y,
....:         inverse_on_support=lambda i: i-1 if i in [2,3,4] else None)
sage: phi(x[1])
B[2] + B[3] + B[4] + B[5]
sage: phi(x[3])
B[4] + B[5]
sage: phi.preimage(y[2] + y[3])
B[1] - B[3]
sage: phi(phi.preimage(y[2] + y[3])) == y[2] + y[3]
True
sage: el = x[1] + 3*x[2] + 2*x[3]
sage: phi.preimage(phi(el)) == el
True

sage: phi.preimage(y[1])
Traceback (most recent call last):
...
ValueError: B[1] is not in the image
sage: phi.preimage(y[4])
Traceback (most recent call last):
...
ValueError: B[4] is not in the image


Over a base ring like $$\ZZ$$, the morphism need not be surjective even when the dimensions match:

sage: X = CombinatorialFreeModule(ZZ, [1, 2, 3]); x = X.basis()
sage: Y = CombinatorialFreeModule(ZZ, [1, 2, 3]); y = Y.basis()
sage: lt = lambda i: sum( 2* y[j] for j in range(i,4)  ) # lower
sage: phi = X.module_morphism(lt, triangular="lower", codomain=Y)
sage: phi.preimage(2*y[1] + 2*y[2])
B[1] - B[3]


The error message in case of failure could be more specific though:

sage: phi.preimage(y[1] + y[2])
Traceback (most recent call last):
...
TypeError: no conversion of this rational to integer

section()#

Return the section (partial inverse) of self.

This returns a partial triangular morphism which is a section of self. The section morphism raises a ValueError if asked to apply on an element which is not in the image of self.

EXAMPLES:

sage: X = CombinatorialFreeModule(QQ, [1,2,3]); x = X.basis()
sage: X.rename('X')
sage: Y = CombinatorialFreeModule(QQ, [1,2,3,4,5]); y = Y.basis()
sage: ult = lambda i: sum(  y[j] for j in range(i+1,6)  ) # uni-lower
sage: phi = X.module_morphism(ult, triangular="lower", codomain=Y,
....:      inverse_on_support=lambda i: i-1 if i in [2,3,4] else None)
sage: ~phi
Traceback (most recent call last):
...
ValueError: Morphism not known to be invertible;
see the invertible option of module_morphism
sage: phiinv = phi.section()
sage: list(map(phiinv*phi, X.basis().list())) == X.basis().list()
True
sage: phiinv(Y.basis()[1])
Traceback (most recent call last):
...
ValueError: B[1] is not in the image

class sage.modules.with_basis.morphism.TriangularModuleMorphismByLinearity(domain, on_basis, codomain=None, category=None, **keywords)#

A concrete class for triangular module morphisms obtained by extending a function by linearity.

class sage.modules.with_basis.morphism.TriangularModuleMorphismFromFunction(domain, function, codomain=None, category=None, **keywords)#

A concrete class for triangular module morphisms implemented by a function.

sage.modules.with_basis.morphism.pointwise_inverse_function(f)#

Return the function $$x \mapsto 1 / f(x)$$.

INPUT:

• f – a function

EXAMPLES:

sage: from sage.modules.with_basis.morphism import pointwise_inverse_function
sage: def f(x): return x
sage: g = pointwise_inverse_function(f)
sage: g(1), g(2), g(3)
(1, 1/2, 1/3)


pointwise_inverse_function() is an involution:

sage: f is pointwise_inverse_function(g)
True
`

Todo

This has nothing to do here!!! Should there be a library for pointwise operations on functions somewhere in Sage?