Algebraic geometry is a mixture of the ideas of two Mediterranean cultures. It is the superposition of the Arab science of the lightening calculation of the solutions of equations over the Greek art of position and shape. This tapestry was originally woven on European soil and is still being refined under the influence of international fashion. Algebraic geometry studies the delicate balance between the geometrically plausible and the algebraically possible. Whenever one side of this mathematical teeter-totter outweighs the other, one immediately loses interest and runs off in search of a more exciting amusement.
George R. Kempf 1944 – 2002
Algebraic Geometry refers to the study of geometric problems via algebraic methods (and sometimes vice versa). While this is a rather old topic, algebraic geometry as understood today is very much a 20th century development. Building on ideas of e.g. Riemann and Dedekind, it was realized that there is an intimate connection between properties of the set of solutions of a system of polynomial equations (called an algebraic variety) and the behavior of the set of polynomial functions on that variety (called the coordinate ring).
As in many geometric disciplines, we can distinguish between local and global questions (and methods). Local investigations in algebraic geometry are essentially equivalent to the study of certain rings, their ideals and modules. This latter topic is also called commutative algebra. It is the basic local toolset of algebraic geometers, in much the same way that differential analysis is the local toolset of differential geometers.
A good conceptual introduction to commutative algebra is [Atiyah69]. An introduction more geared towards computations, and the work most of the algorithms in this module are based on, is [Greuel2008].
This module aims to eventually allow expression and solution of both local and global geometric problems, both in the classical case over a field and in the more modern arithmetic cases. So far, however, there is no geometric functionality at all. Currently the module only provides tools for computational commutative algebra over fields.
All code examples assume:
>>> from sympy import *
>>> x, y, z = symbols('x,y,z')
>>> init_printing(use_unicode=True, wrap_line=False, no_global=True)
In this section we document the usage of the AGCA module. For convenience of the reader, some definitions and examples/explanations are interspersed.
Almost all computations in commutative algebra are relative to a “base ring”. (For example, when asking questions about an ideal, the base ring is the ring the ideal is a subset of.) In principle all polys “domains” can be used as base rings. However, useful functionality is only implemented for polynomial rings over fields, and various localizations and quotients thereof.
As demonstrated in the examples below, the most convenient method to create objects you are interested in is to build them up from the ground field, and then use the various methods to create new objects from old. For example, in order to create the local ring of the nodal cubic \(y^2 = x^3\) at the origin, over \(\mathbb{Q}\), you do:
>>> lr = QQ.poly_ring(x, y, order="ilex") / [y**2 - x**3]
>>> lr
ℚ[x, y, order=ilex]
───────────────────
╱ 3 2╲
╲- x + y ╱
Note how the python list notation can be used as a short cut to express ideals. You can use the convert method to return ordinary sympy objects into objects understood by the AGCA module (although in many cases this will be done automatically – for example the list was automatically turned into an ideal, and in the process the symbols \(x\) and \(y\) were automatically converted into other representations). For example:
>>> X, Y = lr.convert(x), lr.convert(y) ; X
╱ 3 2╲
x + ╲- x + y ╱
>>> x**3 == y**2
False
>>> X**3 == Y**2
True
When no localisation is needed, a more mathematical notation can be used. For example, let us create the coordinate ring of three-dimensional affine space \(\mathbb{A}^3\):
>>> ar = QQ[x, y, z] ; ar
ℚ[x, y, z]
For more details, refer to the following class documentation. Note that the base rings, being domains, are the main point of overlap between the AGCA module and the rest of the polys module. All domains are documented in detail in the polys reference, so we show here only an abridged version, with the methods most pertinent to the AGCA module.
Represents a ring domain.
Attributes
alias | |
dtype | |
one | |
rep | |
zero |
Generate a free module of rank rank over self.
>>> from sympy.abc import x
>>> from sympy import QQ
>>> QQ[x].free_module(2)
QQ[x]**2
Generate an ideal of self.
>>> from sympy.abc import x
>>> from sympy import QQ
>>> QQ[x].ideal(x**2)
<x**2>
Form a quotient ring of self.
Here e can be an ideal or an iterable.
>>> from sympy.abc import x
>>> from sympy import QQ
>>> QQ[x].quotient_ring(QQ[x].ideal(x**2))
QQ[x]/<x**2>
>>> QQ[x].quotient_ring([x**2])
QQ[x]/<x**2>
The division operator has been overloaded for this:
>>> QQ[x]/[x**2]
QQ[x]/<x**2>
Create a generalized multivariate polynomial ring.
A generalized polynomial ring is defined by a ground field \(K\), a set of generators (typically \(x_1, \dots, x_n\)) and a monomial order \(<\). The monomial order can be global, local or mixed. In any case it induces a total ordering on the monomials, and there exists for every (non-zero) polynomial \(f \in K[x_1, \dots, x_n]\) a well-defined “leading monomial” \(LM(f) = LM(f, >)\). One can then define a multiplicative subset \(S = S_> = \{f \in K[x_1, \dots, x_n] | LM(f) = 1\}\). The generalized polynomial ring corresponding to the monomial order is \(R = S^{-1}K[x_1, \dots, x_n]\).
If \(>\) is a so-called global order, that is \(1\) is the smallest monomial, then we just have \(S = K\) and \(R = K[x_1, \dots, x_n]\).
Examples
A few examples may make this clearer.
>>> from sympy.abc import x, y
>>> from sympy import QQ
Our first ring uses global lexicographic order.
>>> R1 = QQ.poly_ring(x, y, order=(("lex", x, y),))
The second ring uses local lexicographic order. Note that when using a single (non-product) order, you can just specify the name and omit the variables:
>>> R2 = QQ.poly_ring(x, y, order="ilex")
The third and fourth rings use a mixed orders:
>>> o1 = (("ilex", x), ("lex", y))
>>> o2 = (("lex", x), ("ilex", y))
>>> R3 = QQ.poly_ring(x, y, order=o1)
>>> R4 = QQ.poly_ring(x, y, order=o2)
We will investigate what elements of \(K(x, y)\) are contained in the various rings.
>>> L = [x, 1/x, y/(1 + x), 1/(1 + y), 1/(1 + x*y)]
>>> test = lambda R: [f in R for f in L]
The first ring is just \(K[x, y]\):
>>> test(R1)
[True, False, False, False, False]
The second ring is R1 localised at the maximal ideal (x, y):
>>> test(R2)
[True, False, True, True, True]
The third ring is R1 localised at the prime ideal (x):
>>> test(R3)
[True, False, True, False, True]
Finally the fourth ring is R1 localised at \(S = K[x, y] \setminus yK[y]\):
>>> test(R4)
[True, False, False, True, False]
Class representing (commutative) quotient rings.
You should not usually instatiate this by hand, instead use the constructor from the ring you are quotienting out by:
>>> from sympy.abc import x
>>> from sympy import QQ
>>> I = QQ[x].ideal(x**3 + 1)
>>> QQ[x].quotient_ring(I)
QQ[x]/<x**3 + 1>
Shorter versions are possible:
>>> QQ[x]/I
QQ[x]/<x**3 + 1>
>>> QQ[x]/[x**3 + 1]
QQ[x]/<x**3 + 1>
Attributes:
Attributes
alias | |
one | |
rep | |
zero |
Let \(A\) be a ring. An \(A\)-module is a set \(M\), together with two binary operations \(+: M \times M \to M\) and \(\times: R \times M \to M\) called addition and scalar multiplication. These are required to satisfy certain axioms, which can be found in e.g. [Atiyah69]. In this way modules are a direct generalisation of both vector spaces (\(A\) being a field) and abelian groups (\(A = \mathbb{Z}\)). A submodule of the \(A\)-module \(M\) is a subset \(N \subset M\), such that the binary operations restrict to \(N\), and \(N\) becomes an \(A\)-module with these operations.
The ring \(A\) itself has a natural \(A\)-module structure where addition and multiplication in the module coincide with addition and multiplication in the ring. This \(A\)-module is also written as \(A\). An \(A\)-submodule of \(A\) is called an ideal of \(A\). Ideals come up very naturally in algebraic geometry. More general modules can be seen as a technically convenient “elbow room” beyond talking only about ideals.
If \(M\), \(N\) are \(A\)-modules, then there is a natural (componentwise) \(A\)-module structure on \(M \times N\). Similarly there are \(A\)-module structures on cartesian products of more components. (For the categorically inclined: the cartesian product of finitely many \(A\)-modules, with this \(A\)-module structure, is the finite biproduct in the category of all \(A\)-modules. With infinitely many components, it is the direct product (but the infinite direct sum has to be constructed differently).) As usual, repeated product of the \(A\)-module \(M\) is denoted \(M, M^2, M^3 \dots\), or \(M^I\) for arbitrary index sets \(I\).
An \(A\)-module \(M\) is called free if it is isomorphic to the \(A\)-module \(A^I\) for some (not necessarily finite) index set \(I\) (refer to the next section for a definition of isomorphism). The cardinality of \(I\) is called the rank of \(M\); one may prove this is well-defined. In general, the AGCA module only works with free modules of finite rank, and other closely related modules. The easiest way to create modules is to use member methods of the objects they are made up from. For example, let us create a free module of rank 4 over the coordinate ring of \(\mathbb{A}^2\) we created above, together with a submodule:
>>> F = ar.free_module(4) ; F
4
ℚ[x, y, z]
>>> S = F.submodule([1, x, x**2, x**3], [0, 1, 0, y]) ; S
╱⎡ 2 3⎤ ╲
╲⎣1, x, x , x ⎦, [0, 1, 0, y]╱
Note how python lists can be used as a short-cut notation for module elements (vectors). As usual, the convert method can be used to convert sympy/python objects into the internal AGCA representation (see detailed reference below).
Here is the detailed documentation of the classes for modules, free modules, and submodules:
Abstract base class for modules.
Do not instantiate - use ring explicit constructors instead:
>>> from sympy import QQ
>>> from sympy.abc import x
>>> QQ[x].free_module(2)
QQ[x]**2
Attributes:
Non-implemented methods:
The method convert likely needs to be changed in subclasses.
Abstract base class for free modules.
Additional attributes:
Non-implemented methods:
Return a set of basis elements.
>>> from sympy.abc import x
>>> from sympy import QQ
>>> QQ[x].free_module(3).basis()
([1, 0, 0], [0, 1, 0], [0, 0, 1])
Convert elem into the internal representation.
This method is called implicitly whenever computations involve elements not in the internal representation.
>>> from sympy.abc import x
>>> from sympy import QQ
>>> F = QQ[x].free_module(2)
>>> F.convert([1, 0])
[1, 0]
alias of FreeModuleElement
Return the identity homomorphism on self.
>>> from sympy.abc import x
>>> from sympy import QQ
>>> QQ[x].free_module(2).identity_hom()
[1, 0]
[0, 1] : QQ[x]**2 -> QQ[x]**2
Returns True if other is a submodule of self.
>>> from sympy.abc import x
>>> from sympy import QQ
>>> F = QQ[x].free_module(2)
>>> M = F.submodule([2, x])
>>> F.is_submodule(F)
True
>>> F.is_submodule(M)
True
>>> M.is_submodule(F)
False
Returns True if self is a zero module.
(If, as this implementation assumes, the coefficient ring is not the zero ring, then this is equivalent to the rank being zero.)
>>> from sympy.abc import x
>>> from sympy import QQ
>>> QQ[x].free_module(0).is_zero()
True
>>> QQ[x].free_module(1).is_zero()
False
Multiply self by the ideal other.
>>> from sympy.abc import x
>>> from sympy import QQ
>>> I = QQ[x].ideal(x)
>>> F = QQ[x].free_module(2)
>>> F.multiply_ideal(I)
<[x, 0], [0, x]>
Return a quotient module.
>>> from sympy.abc import x
>>> from sympy import QQ
>>> M = QQ[x].free_module(2)
>>> M.quotient_module(M.submodule([1, x], [x, 2]))
QQ[x]**2/<[1, x], [x, 2]>
Or more conicisely, using the overloaded division operator:
>>> QQ[x].free_module(2) / [[1, x], [x, 2]]
QQ[x]**2/<[1, x], [x, 2]>
Base class for submodules.
Attributes:
Non-implemented methods:
Methods that likely need change in subclasses:
Convert elem into the internal represantition.
Mostly called implicitly.
>>> from sympy.abc import x
>>> from sympy import QQ
>>> M = QQ[x].free_module(2).submodule([1, x])
>>> M.convert([2, 2*x])
[2, 2*x]
Return the identity homomorphism on self.
>>> from sympy.abc import x
>>> from sympy import QQ
>>> QQ[x].free_module(2).submodule([x, x]).identity_hom()
[1, 0]
[0, 1] : <[x, x]> -> <[x, x]>
Express element e of self in terms of the generators.
>>> from sympy.abc import x
>>> from sympy import QQ
>>> F = QQ[x].free_module(2)
>>> M = F.submodule([1, 0], [1, 1])
>>> M.in_terms_of_generators([x, x**2])
[-x**2 + x, x**2]
Return a homomorphism representing the inclusion map of self.
That is, the natural map from self to self.container.
>>> from sympy.abc import x
>>> from sympy import QQ
>>> QQ[x].free_module(2).submodule([x, x]).inclusion_hom()
[1, 0]
[0, 1] : <[x, x]> -> QQ[x]**2
Returns the intersection of self with submodule other.
>>> from sympy.abc import x, y
>>> from sympy import QQ
>>> F = QQ[x, y].free_module(2)
>>> F.submodule([x, x]).intersect(F.submodule([y, y]))
<[x*y, x*y]>
Some implementation allow further options to be passed. Currently, to only one implemented is relations=True, in which case the function will return a triple (res, rela, relb), where res is the intersection module, and rela and relb are lists of coefficient vectors, expressing the generators of res in terms of the generators of self (rela) and other (relb).
>>> F.submodule([x, x]).intersect(F.submodule([y, y]), relations=True)
(<[x*y, x*y]>, [(y,)], [(x,)])
The above result says: the intersection module is generated by the single element \((-xy, -xy) = -y (x, x) = -x (y, y)\), where \((x, x)\) and \((y, y)\) respectively are the unique generators of the two modules being intersected.
Return True if self is the entire free module.
>>> from sympy.abc import x
>>> from sympy import QQ
>>> F = QQ[x].free_module(2)
>>> F.submodule([x, 1]).is_full_module()
False
>>> F.submodule([1, 1], [1, 2]).is_full_module()
True
Returns True if other is a submodule of self.
>>> from sympy.abc import x
>>> from sympy import QQ
>>> F = QQ[x].free_module(2)
>>> M = F.submodule([2, x])
>>> N = M.submodule([2*x, x**2])
>>> M.is_submodule(M)
True
>>> M.is_submodule(N)
True
>>> N.is_submodule(M)
False
Return True if self is a zero module.
>>> from sympy.abc import x
>>> from sympy import QQ
>>> F = QQ[x].free_module(2)
>>> F.submodule([x, 1]).is_zero()
False
>>> F.submodule([0, 0]).is_zero()
True
Returns the module quotient of self by submodule other.
That is, if self is the module \(M\) and other is \(N\), then return the ideal \(\{f \in R | fN \subset M\}\).
>>> from sympy import QQ
>>> from sympy.abc import x, y
>>> F = QQ[x, y].free_module(2)
>>> S = F.submodule([x*y, x*y])
>>> T = F.submodule([x, x])
>>> S.module_quotient(T)
<y>
Some implementations allow further options to be passed. Currently, the only one implemented is relations=True, which may only be passed if other is prinicipal. In this case the function will return a pair (res, rel) where res is the ideal, and rel is a list of coefficient vectors, expressing the generators of the ideal, multiplied by the generator of other in terms of generators of self.
>>> S.module_quotient(T, relations=True)
(<y>, [[1]])
This means that the quotient ideal is generated by the single element \(y\), and that \(y (x, x) = 1 (xy, xy)\), \((x, x)\) and \((xy, xy)\) being the generators of \(T\) and \(S\), respectively.
Multiply self by the ideal I.
>>> from sympy.abc import x
>>> from sympy import QQ
>>> I = QQ[x].ideal(x**2)
>>> M = QQ[x].free_module(2).submodule([1, 1])
>>> I*M
<[x**2, x**2]>
Return a quotient module.
This is the same as taking a submodule of a quotient of the containing module.
>>> from sympy.abc import x
>>> from sympy import QQ
>>> F = QQ[x].free_module(2)
>>> S1 = F.submodule([x, 1])
>>> S2 = F.submodule([x**2, x])
>>> S1.quotient_module(S2)
<[x, 1] + <[x**2, x]>>
Or more coincisely, using the overloaded division operator:
>>> F.submodule([x, 1]) / [(x**2, x)]
<[x, 1] + <[x**2, x]>>
Reduce the element x of our ring modulo the ideal self.
Here “reduce” has no specific meaning, it could return a unique normal form, simplify the expression a bit, or just do nothing.
Generate a submodule.
>>> from sympy.abc import x
>>> from sympy import QQ
>>> M = QQ[x].free_module(2).submodule([x, 1])
>>> M.submodule([x**2, x])
<[x**2, x]>
Compute the syzygy module of the generators of self.
Suppose \(M\) is generated by \(f_1, \dots, f_n\) over the ring \(R\). Consider the homomorphism \(\phi: R^n \to M\), given by sending \((r_1, \dots, r_n) \to r_1 f_1 + \dots + r_n f_n\). The syzygy module is defined to be the kernel of \(\phi\).
The syzygy module is zero iff the generators generate freely a free submodule:
>>> from sympy.abc import x, y
>>> from sympy import QQ
>>> QQ[x].free_module(2).submodule([1, 0], [1, 1]).syzygy_module().is_zero()
True
A slightly more interesting example:
>>> M = QQ[x, y].free_module(2).submodule([x, 2*x], [y, 2*y])
>>> S = QQ[x, y].free_module(2).submodule([y, -x])
>>> M.syzygy_module() == S
True
Ideals are created very similarly to modules. For example, let’s verify that the nodal cubic is indeed singular at the origin:
>>> I = lr.ideal(x, y)
>>> I == lr.ideal(x)
False
>>> I == lr.ideal(y)
False
We are using here the fact that a curve is non-singular at a point if and only if the maximal ideal of the local ring is principal, and that in this case at least one of \(x\) and \(y\) must be generators.
This is the detailed documentation of the class ideal. Please note that most of the methods regarding properties of ideals (primality etc.) are not yet implemented.
Abstract base class for ideals.
Do not instantiate - use explicit constructors in the ring class instead:
>>> from sympy import QQ
>>> from sympy.abc import x
>>> QQ[x].ideal(x+1)
<x + 1>
Attributes
Non-implemented methods:
Methods that likely should be overridden in subclasses:
Return True if elem is an element of this ideal.
>>> from sympy.abc import x
>>> from sympy import QQ
>>> QQ[x].ideal(x+1, x-1).contains(3)
True
>>> QQ[x].ideal(x**2, x**3).contains(x)
False
Compute the intersection of self with ideal J.
>>> from sympy.abc import x, y
>>> from sympy import QQ
>>> R = QQ[x, y]
>>> R.ideal(x).intersect(R.ideal(y))
<x*y>
Compute the ideal product of self and J.
That is, compute the ideal generated by products \(xy\), for \(x\) an element of self and \(y \in J\).
>>> from sympy.abc import x, y
>>> from sympy import QQ
>>> QQ[x, y].ideal(x).product(QQ[x, y].ideal(y))
<x*y>
Compute the ideal quotient of self by J.
That is, if self is the ideal \(I\), compute the set \(I : J = \{x \in R | xJ \subset I \}\).
>>> from sympy.abc import x, y
>>> from sympy import QQ
>>> R = QQ[x, y]
>>> R.ideal(x*y).quotient(R.ideal(x))
<y>
Reduce the element x of our ring modulo the ideal self.
Here “reduce” has no specific meaning: it could return a unique normal form, simplify the expression a bit, or just do nothing.
Compute the ideal saturation of self by J.
That is, if self is the ideal \(I\), compute the set \(I : J^\infty = \{x \in R | xJ^n \subset I \text{ for some } n\}\).
If \(M\) is an \(A\)-module and \(N\) is an \(A\)-submodule, we can define two elements \(x\) and \(y\) of \(M\) to be equivalent if \(x - y \in N\). The set of equivalence classes is written \(M/N\), and has a natural \(A\)-module structure. This is called the quotient module of \(M\) by \(N\). If \(K\) is a submodule of \(M\) containing \(N\), then \(K/N\) is in a natural way a submodule of \(M/N\). Such a module is called a subquotient. Here is the documentation of quotient and subquotient modules:
Class for quotient modules.
Do not instantiate this directly. For subquotients, see the SubQuotientModule class.
Attributes:
Convert elem into the internal representation.
This method is called implicitly whenever computations involve elements not in the internal representation.
>>> from sympy.abc import x
>>> from sympy import QQ
>>> F = QQ[x].free_module(2) / [(1, 2), (1, x)]
>>> F.convert([1, 0])
[1, 0] + <[1, 2], [1, x]>
alias of QuotientModuleElement
Return the identity homomorphism on self.
>>> from sympy.abc import x
>>> from sympy import QQ
>>> M = QQ[x].free_module(2) / [(1, 2), (1, x)]
>>> M.identity_hom()
[1, 0]
[0, 1] : QQ[x]**2/<[1, 2], [1, x]> -> QQ[x]**2/<[1, 2], [1, x]>
Return True if other is a submodule of self.
>>> from sympy.abc import x
>>> from sympy import QQ
>>> Q = QQ[x].free_module(2) / [(x, x)]
>>> S = Q.submodule([1, 0])
>>> Q.is_submodule(S)
True
>>> S.is_submodule(Q)
False
Return True if self is a zero module.
This happens if and only if the base module is the same as the submodule being killed.
>>> from sympy.abc import x
>>> from sympy import QQ
>>> F = QQ[x].free_module(2)
>>> (F/[(1, 0)]).is_zero()
False
>>> (F/[(1, 0), (0, 1)]).is_zero()
True
Return the quotient homomorphism to self.
That is, return a homomorphism representing the natural map from self.base to self.
>>> from sympy.abc import x
>>> from sympy import QQ
>>> M = QQ[x].free_module(2) / [(1, 2), (1, x)]
>>> M.quotient_hom()
[1, 0]
[0, 1] : QQ[x]**2 -> QQ[x]**2/<[1, 2], [1, x]>
Submodule of a quotient module.
Equivalently, quotient module of a submodule.
Do not instantiate this, instead use the submodule or quotient_module constructing methods:
>>> from sympy.abc import x
>>> from sympy import QQ
>>> F = QQ[x].free_module(2)
>>> S = F.submodule([1, 0], [1, x])
>>> Q = F/[(1, 0)]
>>> S/[(1, 0)] == Q.submodule([5, x])
True
Attributes:
Return True if self is the entire free module.
>>> from sympy.abc import x
>>> from sympy import QQ
>>> F = QQ[x].free_module(2)
>>> F.submodule([x, 1]).is_full_module()
False
>>> F.submodule([1, 1], [1, 2]).is_full_module()
True
Return the quotient homomorphism to self.
That is, return the natural map from self.base to self.
>>> from sympy.abc import x
>>> from sympy import QQ
>>> M = (QQ[x].free_module(2) / [(1, x)]).submodule([1, 0])
>>> M.quotient_hom()
[1, 0]
[0, 1] : <[1, 0], [1, x]> -> <[1, 0] + <[1, x]>, [1, x] + <[1, x]>>
Let \(M\) and \(N\) be \(A\)-modules. A mapping \(f: M \to N\) satisfying various obvious properties (see [Atiyah69]) is called an \(A\)-module homomorphism. In this case \(M\) is called the domain and N the codomain. The set \(\{x \in M | f(x) = 0\}\) is called the kernel \(ker(f)\), whereas the set \(\{f(x) | x \in M\}\) is called the image \(im(f)\). The kernel is a submodule of \(M\), the image is a submodule of \(N\). The homomorphism \(f\) is injective if and only if \(ker(f) = 0\) and surjective if and only if \(im(f) = N\). A bijective homomorphism is called an isomorphism. Equivalently, \(ker(f) = 0\) and \(im(f) = N\). (A related notion, which currently has no special name in the AGCA module, is that of the cokernel, \(coker(f) = N/im(f)\).)
Suppose now \(M\) is an \(A\)-module. \(M\) is called finitely generated if there exists a surjective homomorphism \(A^n \to M\) for some \(n\). If such a morphism \(f\) is chosen, the images of the standard basis of \(A^n\) are called the generators of \(M\). The module \(ker(f)\) is called syzygy module with respect to the generators. A module is called finitely presented if it is finitely generated with a finitely generated syzygy module. The class of finitely presented modules is essentially the largest class we can hope to be able to meaningfully compute in.
It is an important theorem that, for all the rings we are considering, all submodules of finitely generated modules are finitely generated, and hence finitely generated and finitely presented modules are the same.
The notion of syzygies, while it may first seem rather abstract, is actually very computational. This is because there exist (fairly easy) algorithms for computing them, and more general questions (kernels, intersections, ...) are often reduced to syzygy computation.
Let us say a few words about the definition of homomorphisms in the AGCA module. Suppose first that \(f : M \to N\) is an arbitrary morphism of \(A\)-modules. Then if \(K\) is a submodule of \(M\), \(f\) naturally defines a new homomorphism \(g: K \to N\) (via \(g(x) = f(x)\)), called the restriction of \(f\) to \(K\). If now \(K\) contained in the kernel of \(f\), then moreover \(f\) defines in a natural homomorphism \(g: M/K \to N\) (same formula as above!), and we say that \(f\) descends to \(M/K\). Similarly, if \(L\) is a submodule of \(N\), there is a natural homomorphism \(g: M \to N/L\), we say that \(g\) factors through \(f\). Finally, if now \(L\) contains the image of \(f\), then there is a natural homomorphism \(g: M \to L\) (defined, again, by the same formula), and we say \(g\) is obtained from \(f\) by restriction of codomain. Observe also that each of these four operations is reversible, in the sense that given \(g\), one can always (non-uniquely) find \(f\) such that \(g\) is obtained from \(f\) in the above way.
Note that all modules implemented in AGCA are obtained from free modules by taking a succession of submodules and quotients. Hence, in order to explain how to define a homomorphism between arbitrary modules, in light of the above, we need only explain how to define homomorphisms of free modules. But, essentially by the definition of free module, a homomorphism from a free module \(A^n\) to any module \(M\) is precisely the same as giving \(n\) elements of \(M\) (the images of the standard basis), and giving an element of a free module \(A^m\) is precisely the same as giving \(m\) elements of \(A\). Hence a homomorphism of free modules \(A^n \to A^m\) can be specified via a matrix, entirely analogously to the case of vector spaces.
The functions restrict_domain etc. of the class Homomorphism can be used to carry out the operations described above, and homomorphisms of free modules can in principle be instantiated by hand. Since these operations are so common, there is a convenience function homomorphism to define a homomorphism between arbitrary modules via the method outlined above. It is essentially the only way homomorphisms need ever be created by the user.
Create a homomorphism object.
This function tries to build a homomorphism from domain to codomain via the matrix matrix.
Examples
>>> from sympy import QQ, homomorphism
>>> from sympy.abc import x
>>> R = QQ[x]
>>> T = R.free_module(2)
If domain is a free module generated by \(e_1, \dots, e_n\), then matrix should be an n-element iterable \((b_1, \dots, b_n)\) where the \(b_i\) are elements of codomain. The constructed homomorphism is the unique homomorphism sending \(e_i\) to \(b_i\).
>>> F = R.free_module(2)
>>> h = homomorphism(F, T, [[1, x], [x**2, 0]])
>>> h
[1, x**2]
[x, 0] : QQ[x]**2 -> QQ[x]**2
>>> h([1, 0])
[1, x]
>>> h([0, 1])
[x**2, 0]
>>> h([1, 1])
[x**2 + 1, x]
If domain is a submodule of a free module, them matrix determines a homomoprhism from the containing free module to codomain, and the homomorphism returned is obtained by restriction to domain.
>>> S = F.submodule([1, 0], [0, x])
>>> homomorphism(S, T, [[1, x], [x**2, 0]])
[1, x**2]
[x, 0] : <[1, 0], [0, x]> -> QQ[x]**2
If domain is a (sub)quotient \(N/K\), then matrix determines a homomorphism from \(N\) to codomain. If the kernel contains \(K\), this homomorphism descends to domain and is returned; otherwise an exception is raised.
>>> homomorphism(S/[(1, 0)], T, [0, [x**2, 0]])
[0, x**2]
[0, 0] : <[1, 0] + <[1, 0]>, [0, x] + <[1, 0]>, [1, 0] + <[1, 0]>> -> QQ[x]**2
>>> homomorphism(S/[(0, x)], T, [0, [x**2, 0]])
Traceback (most recent call last):
...
ValueError: kernel <[1, 0], [0, 0]> must contain sm, got <[0,x]>
Finally, here is the detailed reference of the actual homomorphism class:
Abstract base class for module homomoprhisms. Do not instantiate.
Instead, use the homomorphism function:
>>> from sympy import QQ, homomorphism
>>> from sympy.abc import x
>>> F = QQ[x].free_module(2)
>>> homomorphism(F, F, [[1, 0], [0, 1]])
[1, 0]
[0, 1] : QQ[x]**2 -> QQ[x]**2
Attributes:
Non-implemented methods:
Compute the image of self.
That is, if self is the homomorphism \(\phi: M \to N\), then compute \(im(\phi) = \{\phi(x) | x \in M \}\). This is a submodule of \(N\).
>>> from sympy import homomorphism, QQ
>>> from sympy.abc import x
>>> F = QQ[x].free_module(2)
>>> homomorphism(F, F, [[1, 0], [x, 0]]).image() == F.submodule([1, 0])
True
Return True if self is injective.
That is, check if the elements of the domain are mapped to the same codomain element.
>>> from sympy import homomorphism, QQ
>>> from sympy.abc import x
>>> F = QQ[x].free_module(2)
>>> h = homomorphism(F, F, [[1, 0], [x, 0]])
>>> h.is_injective()
False
>>> h.quotient_domain(h.kernel()).is_injective()
True
Return True if self is an isomorphism.
That is, check if every element of the codomain has precisely one preimage. Equivalently, self is both injective and surjective.
>>> from sympy import homomorphism, QQ
>>> from sympy.abc import x
>>> F = QQ[x].free_module(2)
>>> h = homomorphism(F, F, [[1, 0], [x, 0]])
>>> h = h.restrict_codomain(h.image())
>>> h.is_isomorphism()
False
>>> h.quotient_domain(h.kernel()).is_isomorphism()
True
Return True if self is surjective.
That is, check if every element of the codomain has at least one preimage.
>>> from sympy import homomorphism, QQ
>>> from sympy.abc import x
>>> F = QQ[x].free_module(2)
>>> h = homomorphism(F, F, [[1, 0], [x, 0]])
>>> h.is_surjective()
False
>>> h.restrict_codomain(h.image()).is_surjective()
True
Return True if self is a zero morphism.
That is, check if every element of the domain is mapped to zero under self.
>>> from sympy import homomorphism, QQ
>>> from sympy.abc import x
>>> F = QQ[x].free_module(2)
>>> h = homomorphism(F, F, [[1, 0], [x, 0]])
>>> h.is_zero()
False
>>> h.restrict_domain(F.submodule()).is_zero()
True
>>> h.quotient_codomain(h.image()).is_zero()
True
Compute the kernel of self.
That is, if self is the homomorphism \(\phi: M \to N\), then compute \(ker(\phi) = \{x \in M | \phi(x) = 0\}\). This is a submodule of \(M\).
>>> from sympy import homomorphism, QQ
>>> from sympy.abc import x
>>> F = QQ[x].free_module(2)
>>> homomorphism(F, F, [[1, 0], [x, 0]]).kernel()
<[x, -1]>
Return self with codomain replaced by codomain/sm.
Here sm must be a submodule of self.codomain.
>>> from sympy import homomorphism, QQ
>>> from sympy.abc import x
>>> F = QQ[x].free_module(2)
>>> h = homomorphism(F, F, [[1, 0], [x, 0]])
>>> h
[1, x]
[0, 0] : QQ[x]**2 -> QQ[x]**2
>>> h.quotient_codomain(F.submodule([1, 1]))
[1, x]
[0, 0] : QQ[x]**2 -> QQ[x]**2/<[1, 1]>
This is the same as composing with the quotient map on the left:
>>> (F/[(1, 1)]).quotient_hom() * h
[1, x]
[0, 0] : QQ[x]**2 -> QQ[x]**2/<[1, 1]>
Return self with domain replaced by domain/sm.
Here sm must be a submodule of self.kernel().
>>> from sympy import homomorphism, QQ
>>> from sympy.abc import x
>>> F = QQ[x].free_module(2)
>>> h = homomorphism(F, F, [[1, 0], [x, 0]])
>>> h
[1, x]
[0, 0] : QQ[x]**2 -> QQ[x]**2
>>> h.quotient_domain(F.submodule([-x, 1]))
[1, x]
[0, 0] : QQ[x]**2/<[-x, 1]> -> QQ[x]**2
Return self, with codomain restricted to to sm.
Here sm has to be a submodule of self.codomain containing the image.
>>> from sympy import homomorphism, QQ
>>> from sympy.abc import x
>>> F = QQ[x].free_module(2)
>>> h = homomorphism(F, F, [[1, 0], [x, 0]])
>>> h
[1, x]
[0, 0] : QQ[x]**2 -> QQ[x]**2
>>> h.restrict_codomain(F.submodule([1, 0]))
[1, x]
[0, 0] : QQ[x]**2 -> <[1, 0]>
Return self, with the domain restricted to sm.
Here sm has to be a submodule of self.domain.
>>> from sympy import homomorphism, QQ
>>> from sympy.abc import x
>>> F = QQ[x].free_module(2)
>>> h = homomorphism(F, F, [[1, 0], [x, 0]])
>>> h
[1, x]
[0, 0] : QQ[x]**2 -> QQ[x]**2
>>> h.restrict_domain(F.submodule([1, 0]))
[1, x]
[0, 0] : <[1, 0]> -> QQ[x]**2
This is the same as just composing on the right with the submodule inclusion:
>>> h * F.submodule([1, 0]).inclusion_hom()
[1, x]
[0, 0] : <[1, 0]> -> QQ[x]**2